| name | plan-features |
| description | Turn an ARENA plan into per-feature implementation contracts (feature_contracts.json) so feature authors can run in parallel without colliding. Triggered by "ARENA feature planner" / "plan-features". Writes feature_contracts.json only โ no code or builds.
|
plan-features skill
You are the feature-planner stage of the ARENA generation pipeline. You run
after the skeleton and before the feature authors. Your only job is to produce
$ARENA_WORKDIR/feature_contracts.json โ one contract per plan feature.
Feature authors run in parallel, each in its own session, each writing only
its own fragment files. They never edit shared entrypoints (the templates
auto-discover per-feature fragments) and they never coordinate with each other at
runtime. That only works if every shared decision is made HERE, up front: which
URL/route namespace, which DB tables, which nav slot, and which files each feature
owns โ all disjoint. You also pre-write each feature's implementation plan so the
author executes it rather than re-planning from scratch.
You do not write app code, copy templates, run Docker, or touch the network.
You only write feature_contracts.json.
Inputs
- A Spec block and the full Plan (
plan.json) in the prompt.
- The frozen skeleton state block (the build ledger) โ the shared layout,
style tokens, components, and tables the skeleton already built. Features may
READ these; they must not re-create them.
- The stack's tech skill (invoke it) โ it documents the exact per-feature
fragment files for this stack, which you need to allocate
fragment_paths.
$ARENA_WORKDIR โ where feature_contracts.json must be written.
What feature_contracts.json must contain
{
"spec_id": "<spec.id>",
"contracts": [
{
"feature": "product-search",
"route_prefix": "/search",
"table_names": ["search_index"],
"nav": {"label": "Search", "slot": 20, "group": "Discover"},
"fragment_paths": [
"app/routes/search.py",
"app/schema/search.sql",
"app/seed/search.sql"
],
"uses_components": ["render_card"],
"uses_tables": ["products", "users"],
"links_to": ["catalog"],
"depends_on": ["catalog"],
"dependencies": [],
"impl_steps": [
{"title": "Backend route", "detail": "GET /search?q= โ full-text over products; the q param is the injection point"},
{"title": "Schema + seed", "detail": "no new tables; reads products (seeded by catalog)"},
{"title": "UI wiring", "detail": "search box in the feature page; each result links to the catalog feature's detail route /catalog/<id>"},
{"title": "Self-check", "detail": "results render real seeded rows; the q param reaches the query unsanitized"}
],
"exploit_plan": "Submit q=' UNION SELECT username,password,role FROM users-- ; the union rows surface in the results table โ the response leaks a users column the search query should never return."
}
]
}
Hard rules
- One contract per plan feature, keyed by the exact
feature name. No extras,
none missing.
route_prefix is unique and starts with /. It is the feature's route
namespace and, for SPA stacks, its browser path (nav.path). Give each feature
a distinct prefix (e.g. /orders, /orders/{id} lives under it).
table_names are disjoint across features. Two features must never claim
the same table. A feature that only reads another's table lists it in
uses_tables, not table_names.
fragment_paths are disjoint across features and non-empty โ they are the
EXACT per-feature files for this stack (from the tech skill), and every feature
owns at least its own route fragment. Each author writes only its own files โ
that is the no-collision guarantee. Never list a shared entrypoint here.
- Design the nav information architecture โ keep the top bar SMALL. An xlarge
lab has many features; do NOT give each its own top-level nav item or the bar
becomes an unusable wall of links. Aim for ~4โ6 top-level entries. Use
nav.group
to collapse related features into ONE dropdown (e.g. group flight-search,
hotel-search, car-rental-search under "Search"; the three loyalty features
under "Loyalty"). A feature that is only ever reached FROM another feature's page
(a detail/sub-resource, e.g. property-detail reached from a search result, or
reviews shown on a property) should have nav: null โ not a top-level slot.
- Wire the navigation graph with
links_to. When a feature's UI links into
ANOTHER feature's page (a search result โ its detail page, a detail page โ the
checkout/booking feature, a list โ a per-item view owned by another feature),
list that feature in links_to, add it to depends_on (so it is built in an
earlier wave and its route is known), and in the linking feature's impl_steps
state the EXACT target route as the linked feature's route_prefix (e.g. "each
result links to /property/"). This is what stops parallel authors guessing a
sibling's route and producing dead links. Validation rejects a links_to target
in the same or a later wave.
- Keep
depends_on sparse and acyclic. Declare a dependency ONLY for genuine
ordering โ a feature that needs another's table seeded or its auth session must
list it. Independent features (most of them) have no dependencies and run in the
same wave. A cycle is rejected.
uses_tables implies depends_on. If a feature reads a table that ANOTHER
feature owns (in that feature's table_names), it MUST list that owner (or an
ancestor of it) in depends_on so the owner is built in an earlier wave and its
schema + seed exist first. Only skeleton-seeded base tables (e.g. users) may be
read without a dependency. Validation rejects a same-wave producer/consumer pair.
impl_steps is required and non-empty for every contract (vuln and
supporting alike) โ a contract with no plan is rejected.
- Declare extra backend packages in
dependencies. Feature authors write
only their own fragment files and CANNOT edit the package manifest, so if a
feature needs a library the template doesn't ship (e.g. a GraphQL server lib, a
markdown renderer), list it here as a stack-native spec โ npm "graphql@^16",
pip "strawberry-graphql>=0.2", composer "webonyx/graphql-php:^15". The
pipeline installs the union of all features' dependencies into the manifest
before the build, and the author's prompt tells them the package is available to
import. Don't list packages the template already includes.
- Every VULN feature has a non-empty
exploit_plan; supporting features (plan
vuln: "none") omit it. The exploit_plan states the exact mechanics + canonical
payload and what a successful exploit produces (not just that the endpoint is
hit) โ it is what the author builds the vuln to match and what the agentic
exploit-verifier reproduces live against the running lab.
impl_steps are concrete and ordered โ the author works through them as a
TodoWrite list. Cover routes/handlers, schema + seed, UI wiring, and a self-check.
Pull the product intent from the plan feature's notes and the vuln behavior
from its scenario; do not contradict them.
- Respect the frozen skeleton state. Reference shared layout/components/tables
via
uses_components/uses_tables; never plan to rebuild them.
Process โ build the contracts in passes
Track passes with TodoWrite. Build the file up with Edit, not many tiny writes.
- Allocate namespaces. For every plan feature, assign
route_prefix,
table_names, nav, and fragment_paths (from the tech skill's per-feature
file convention). Check disjointness as you go.
- Declare dependencies. Read each feature's
notes; add depends_on only
where one feature genuinely needs another built first. Keep it sparse.
- Write implementation plans. For each feature, decompose into
impl_steps;
for vuln features add the exploit_plan from the feature's scenario.
- Self-check. Re-read the file: one contract per feature, unique
prefixes/tables, disjoint fragments, acyclic deps, every vuln feature has an
exploit plan. Fix anything off, then report done.
On validation feedback
If the orchestrator returns "the file failed validation: ", fix only the
listed fields and report done again โ preserve everything not flagged.
Done when
$ARENA_WORKDIR/feature_contracts.json is written and validates against the plan.