Skip to content
Back to skills

Code To Spec

ASecurity

Use when writing the AEP specs of a product that already exists as code — the pass that reads a codebase and writes specs/requirements/ (with a coverage file per module) and specs/design/domain-model.md, so /design can take it from there. Runs from any developer's Claude Code, never in a platform turn.

  • 24 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 11, 2026
developmentgoshellsqlnodeawsgitapidatabasefrontendbackend

Works with

  • claude code
  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 10 files and shows the line behind each finding

Scanned November 11, 2026

npx -y skills add wso2/labs-agentic-engineer --skill code-to-spec --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Code To Spec?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Code To Spec
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/wso2-code-to-spec/badge)](https://www.skillsdirectory.com/skills/wso2-code-to-spec)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: code-to-spec
description: Use when writing the AEP specs of a product that already exists as code — the pass that reads a codebase and writes specs/requirements/ (with a coverage file per module) and specs/design/domain-model.md, so /design can take it from there. Runs from any developer's Claude Code, never in a platform turn.
argument-hint: <product root> [output dir]
---

# Code to spec

A product exists as code and is going to be recreated on AEP. This skill
replaces the two flows that would otherwise ask a human what the product
does — `start` and `interview` — because the code already answers them. It
writes the requirements and the domain model; **`/design` writes everything
else**, exactly as it does for a product that started from an idea.

It runs in a developer's Claude Code, from any directory: the platform
contracts it follows travel with it under `references/`. If you were loaded
inside a platform turn — no shell, no file reads — stop and say this skill
is run from a developer's terminal.

## Read the contracts first

Three files beside this one govern every line you write. Read all three now;
this skill restates none of them, and a rule remembered instead of read drifts.

- `references/prd-contract.md` — every requirements file, the line grammar,
  the IDs, the `sources/` coverage file, what the requirements exclude.
- `references/domain-model-shape.md` — the shape of
  `specs/design/domain-model.md`.
- `references/dependency-shape.md` — the shape of
  `specs/design/dependencies/<name>/dependency.json`.

All three are the platform's own skills copied verbatim and pinned by a test
in the platform repository, so what you read is the contract the platform
enforces. Never edit them here. `references/bundle-format.md` is this skill's
own: the archive the platform seeds a project from.

## What this writes, and what it never writes

```
<out>/specs/requirements/prd.md
<out>/specs/requirements/product-wide.md          (+ product-wide/<topic>.md when a topic grows)
<out>/specs/requirements/features/F<n>-<slug>.md
<out>/specs/requirements/sources/<module>.md      one per module, one per document
<out>/specs/design/domain-model.md
<out>/specs/design/dependencies/<name>/dependency.json   one per upstream the recreate keeps calling
<out>/specs/design/dependencies/<name>/schema.graphql    its derived contract (or openapi.yaml)
<out>/specs/design/dependencies/<name>/assumptions.md    what the derivation relies on
<out>/<product>.aespecs                           the bundle the platform seeds the project from
<out>/registrations/<name>.md                     what a definition cannot hold: instructions, fronts, used by
<out>/.inventory/*.md                             working notes; never pushed
```

Never `design.cell`, a `design.json`, `security.json`, `flows/`, a
component's `openapi.yaml`, `wireframes.dsl` or acceptance criteria. Each
encodes a decision the platform makes from the requirements with catalog
lookups and write gates you do not have here; written from legacy code they
would hand the first design a topology nobody chose. The as-is engineering is
recorded, but in the coverage files, where it informs without becoming the
design. The one exception is a dependency definition: a fact about a system
that exists, which the first design references rather than decides.

## Arguments

- **Product root** — the directory that is the product: a repository, or one
  app inside a monorepo (`…/digiops-finance/apps/allocation`). Everything
  below it is the product; nothing above it is, with one exception under
  **Read scope**.
- **Output dir** — defaults to `<basename of the product root>-specs/` under
  the current working directory. Refuse an output directory inside the
  product root, and never write into the product's own repository. Someone
  working in the platform repository may point it at the playground's
  projects directory, which git ignores there, to run the local design phase
  on the result. The product's name for `prd.md` comes from its documents,
  else from the root's basename.

## Read scope

Stories, decisions and rules come from the product root only. When a service
the product calls has its source in the same tree — a sibling directory, an
`entity/` or `services/` folder — read **only its schema or contract files**
(GraphQL schema, OpenAPI document, record types) to name the entities and
fields the product uses, and cite them. Never derive a story from a
dependency's code: the product boundary is the root, and a dependency's
behaviour is its own product's requirement.

Configuration files yield **key names only**. A value — a URL, a secret, an
address — never reaches a spec or an inventory. A `Config.toml.local` with
real credentials is an ordinary input; copying one line of it is a leak.

## The inventory pass — before any writing

Nothing can be cut into features before the whole product is in view, and a
codebase is too large to hold in one read. Build six inventories first, each a
markdown list of points in the form `- <file>:<line or symbol> — <fact, in the
code's own terms>`, written to `<out>/.inventory/<name>.md`. The folder is
dot-led on purpose: every turn snapshot, local or platform, drops dot-led
paths, so a `/design` run never inlines the working notes.

| Inventory | What it lists | Where it hides |
|---|---|---|
| `surfaces` | every route, page, screen, navigation entry, scheduled job, CLI verb | routers, resource functions, page components, nav menus, cron |
| `access` | roles or groups, privileges or permissions, which surface each gates, how roles map to privileges | auth middleware, interceptors, route guards, privilege constants |
| `data` | record types, tables, entities fetched from other services, their fields and relations, status enums | type modules, migrations, ORM models, GraphQL queries |
| `integrations` | every outbound client: what it reaches and what that fronts, its auth style, its config key names, every operation the product calls with its request and response shapes | client constructors, config records, SDK imports, HTTP clients, mail senders |
| `documents` | every README, ADR, doc page, inline design note, with its claims | `README*`, `docs/`, `design/`, long header comments |
| `rules` | constants, validation patterns, limits, user-facing message strings that state a rule | constants modules, validators, i18n or message files |

When the product has more source than you can read in one pass, run the six
as **parallel subagents**, one per inventory, each told the product root, the
read-scope rule and the point format, each returning its list. Otherwise read
directly. Either way the inventory files exist before step two starts; a
feature cut made from a partial view puts stories in the wrong feature, and
moving them later retires IDs.

## The feature cut

A feature is a capability a user would recognise by name (`prd-contract`,
"What a feature is"). Cut from the `surfaces` inventory:

- **Cluster surfaces by the job a user comes to do**, not by the directory
  that implements them. A package or module name is evidence for a source
  tag, never a boundary; one module routinely serves three features and one
  feature routinely spans backend and frontend.
- **A shared concept is its own feature** for the stories about the concept
  itself — a settings page that manages allocation types is a feature named
  for allocation types; each use of a type stays with the feature that uses it.
- **A grab-bag surface splits.** "Settings" and "Admin" pages are several
  features wearing one menu entry.
- **The documents' feature list is a seed.** Where a README names features,
  start from its names; where the code has a capability the documents lack,
  it is still a feature, and the gap is an open question for the product page.

Write the cut to `<out>/.inventory/features.md`: each feature, its one-line
purpose, and every surface, access point and rule assigned to it. A surface
assigned to no feature, or to two, is a defect to fix here, not later.

## Writing the requirements

The contract governs the files. What the contract leaves to the author of a
code-derived spec is settled here, once.

**Source tags.** Every line that code or a document states carries one, in
the contract's `[<document> · <place>]` form with the module as the document
and the file as the place:

```
- F2.3 As a manager, I approve or reject a claim with a reason. [backend · service.bal:312]
- P1 Every household member signs in through the organization's SSO. [webapp · oauth.js] Applies to: all.
- A claim over 1,000 in the company currency also needs Finance. [README.md · Features]
```

The module is the path relative to the product root; a document is its file
name. `[org default]` is the platform's tag for the organization skill's
defaults and is never yours to write.

**Actors** come from the `access` inventory: a role or group that gates a
surface is an actor, named in product language ("Manager", not
`CUSTOMER_ENGAGEMENT_APPROVER`). Define each once in `prd.md`.

**Stories** come from surfaces: one line per thing a user does, naming its
actor, in the user's words. A route that only serves another route's data is
not a story; the story is what the screen lets the user do.

**Decisions** come from the `rules` inventory and from status enums: a
validation pattern, a numeric limit, an email-routing rule, a status
transition is one Decision line each, in the feature it governs. A rule too
long for a line goes in that file's `## Further Notes`; a topic that spans
features and outgrows `product-wide.md` moves to `product-wide/<topic>.md`.
The requirements are the only route a rule has to the build — `/design` mints
the acceptance criteria from them alone — so a rule left in a coverage file
does not exist to the recreate.

**Translate integrations into the platform's vocabulary** rather than copying
the legacy construct:

| The code has | The requirements say |
|---|---|
| a call to an LLM API for some task | a Decision naming what the agent does: "Skill recommendations: suggested by an agent." No provider, no model |
| a client to a third-party SaaS or to another in-house service | a Decision naming the capability and the resource by its proposed registered name, with what it fronts: "Customers, opportunities and products: `sales-entity-service`, the organization's sales entity service, which fronts Salesforce." The name is the definition's (**Dependency definitions**). A service that fronts another system also gets an Open Question on the product page: keep calling the service, or go to the system behind it |
| sign-in via directory groups or an IdP | a P item: "Every user signs in through the organization's SSO." Roles as actors |
| a runtime screen that edits role → privilege mappings | stories for what the screen does, and an Open Question whether editing privileges at runtime is a requirement or an artifact of the old directory, since the platform fixes scopes at design |
| synthetic identities (pseudo-emails, service accounts standing in for people) | a Decision stating the need they serve, and an Open Question on how the recreate identifies those people |

**`*assumed*`** closes every line you inferred rather than read: a feature's
Purpose beyond naming the capability, a role's intent, an Out of Scope line
no document states, the reading of a quirk as deliberate. A line the code or
a document states carries its source and no tag. The console draws its Settle
control on assumed lines, so the tag is how the team reviews your inferences.

**Open Questions** hold what only the team knows: whether a behaviour is
intended or a workaround, whether an undocumented capability is wanted in the
recreate, what a disabled or half-built path was for. Where a document and the
code disagree, write what the code does and raise the disagreement as the
question. Use `*blocking*` only as the contract defines it, when every story
of a feature waits on the answer; expect it rarely.

**Fog, Out of Scope.** A capability behind a feature flag, a stub or an
unreachable path is Fog with an open question, not a feature. Out of Scope
takes what a document states the product does not do, and what the code
explicitly refuses when a user would expect it; the latter is `*assumed*`.

**What stays out of the requirements:** how it is built. Pagination loops,
retry policies, connection pools, header names, the database split, the
hand-written authorization table. The recreate decides those under the
platform's conventions. They go to the coverage file as `not used` points; one
that turns out to encode a product constraint comes back as a Decision.

## Coverage files — `sources/<module>.md`

The contract's coverage file, with a module standing where an attached
document stands: how the team sees that their code was read, and the one
place that says what was set aside. One per module and one per document.

- **A module** is a directory under the product root at the granularity that
  gives a file of roughly twenty to eighty lines — `backend/`, `webapp/`, or
  a backend's `modules/email/` when the top level is too coarse. The title is
  the module's path relative to the root. No `Pages:` line. Its second line
  records where the code was read, so the line references can be resolved
  later: `Read from <the product root's git remote, or "a local checkout"> at
  <short commit>`.
- **A document** keeps the contract's shape exactly: its file name as the
  title, `Pages:` when it has pages, its sections as places.
- One line per point, in file order, each ending in an arrow:

```
- service.bal:312 (patch engagements/{id}): completes, reopens or cancels an engagement; cancelling cancels its allocations → F3.4, F3.5
- service.bal:561 (get user-privileges): the caller's privileges from their roles → P2
- jwt_interceptor.bal:67: path-by-path privilege table → not used: how it is built; roles and gates are F7
- README.md · Features §2: "Fetch new updated dates from salesforce" → F2.6
- README.md · API: documents 10 of 60 endpoints → not used: superseded by the code
```

Every point in the six inventories appears exactly once across the coverage
files. A `not used` reason is one of: *how it is built*, *superseded by the
code (<file>)*, *dead path*, *test scaffolding*, *outside the product root*.

The design agent reads every `.md` under `specs/` as context, so a coverage
file is where `/design` learns the as-is shape — keep each line to one point
and resist prose.

## Dependency definitions — `specs/design/dependencies/<name>/`

The recreate keeps calling the systems the product calls today, and the
first design settles such a system only when a definition of it already
exists. So for every outbound client to a system the recreate must keep
talking to — in-house services and third-party SaaS alike — write its
definition in the platform's own shape, which the `/recreate` flow references
and never rewrites. Not the product's own databases (the design introduces
its own), not the LLM call (an agent decision), not the IdP (a P item).

- **Name** — kebab-case (`^[a-z0-9]+(?:-[a-z0-9]+)*$`); the directory,
  `name` and `resource.name` all equal; naming the thing by its own identity
  — the code's display label or client name, `sales-entity-service` — not
  this product's use of it. The name becomes the cell node and the
  component's reference, and the organization's record if it is promoted.
- **Shape** — `references/dependency-shape.md` is the platform's own
  definition of the file. Write `name`, `resource.name`,
  `resource.description` (what it is and what it fronts, in the requirements'
  words), `resource.provider` (the concrete system: "the organization's
  sales entity service, backed by Salesforce"), `resource.config` and
  `resource.contract`. Never `ref`, `suggestions`, `provenance` or
  `consumptionInstructions`: the first three are the platform's or the design
  turn's to write, and the last is the organization's field, refused from a
  project. Nothing else: the platform refuses unknown keys.
- **Keys** — `resource.config`, one `{ "key", "description", "secret" }` per
  key; one set per definition even when the legacy shares one credential set
  across services; upper snake case, prefixed with the name
  (`SALES_ENTITY_SERVICE_CLIENT_SECRET`), because each key lands in the
  consumer as an environment variable as-is. Every key gets a one-line "what
  it is" and a secret flag. Key names come from the client's config record;
  values are never written anywhere — the platform collects them per
  environment after the design.
- **Contract** — derive one when the code supports it, beside the definition,
  under the file name the platform fixes: `schema.graphql` for a GraphQL
  client, `openapi.yaml` for a REST client, and `resource.contract` is
  `{ "type": "graphql" | "openapi", "path": "<that file name>", "origin": "derived" }`.
  The GraphQL file is an SDL holding only the operations and fields the
  product uses, type names as the queries spell them, a sibling service's
  record types read for field types only; the OpenAPI file holds only the
  called paths and their record shapes. No `servers` entry, no URL, no
  example value from config or test data. When nothing can be derived, omit
  `resource.contract`; the design then asks for the document.
- **`assumptions.md`** — beside the definition, in the platform's shape (an
  H1 "What the design assumes about <name>", then one `*assumed*` line per
  fact): that the contract was derived from the product's client code at the
  read commit and is not the service's published schema, and any behaviour
  the product relies on that nothing confirms. It is where the first design
  reads what the derivation rests on.
- **The brief** — `registrations/<name>.md`, in the shape
  `references/registration-brief.md` fixes, holds what a definition cannot:
  the consumption instructions a consumer follows (the auth style, the
  operations, envelopes and quirks, what the service fronts), what it fronts,
  and the stories that reach it. It stays out of the bundle; an organization
  pastes the instructions when it promotes the resource.

Nothing is sent anywhere. The skill writes files; the platform collects the
values from the person who holds them.

## The bundle — `<out>/<product>.aespecs`

The platform seeds a project from the `specs/` tree packed as an `.aespecs`
bundle (`references/bundle-format.md`). After the self-check, run

```
node <this skill's directory>/scripts/bundle.mjs <out> --product "<name>" --repository "<the product root's git remote, or 'local checkout'>" --commit <short commit>
```

It packs `manifest.json` and only the paths the format allows, refuses a stray
file rather than skipping it, and prints the bundle's path. The briefs and the
working notes never enter it.

## `specs/design/domain-model.md`

From the `data` inventory, in the shape `references/domain-model-shape.md`
fixes: an H1, one or two sentences, exactly one mermaid `erDiagram`, a few
lines of entity notes. Include every entity a user would name, including
lookup tables that are product concepts (allocation types, categories);
exclude infrastructure (sessions, migrations, audit tables, job queues). Key
fields only; relations with their cardinality; an entity that lives in another
service is still an entity here if the product's stories act on it. Status
values are Decisions, not notes.

## Self-check before closing

Run these over `<out>/specs/`, and fix what they find:

- **IDs**: no duplicate `F<n>.<m>` or `P<n>`; features numbered in creation
  order; each feature's stories numbered from 1 with no gaps, since nothing
  has been retired yet.
- **Actors**: every actor a story names is defined in `prd.md`.
- **Tails**: every story, decision and P item carries a source tag or
  `*assumed*`; every P item carries `Applies to:`.
- **Arrows**: every coverage line ends in `→ <ID>` or `→ not used: <reason>`.
- **Vocabulary**: `grep -rniwE 'http|https|sql|mysql|jwt|graphql|endpoint|token|json|regex' <out>/specs/requirements` — each hit is rewritten in product language or moved to a coverage file.
- **Secrets**: `grep -rniE 'secret|password|api[-_ ]?key|bearer' <out>/specs <out>/registrations <out>/.inventory` — a hit may be a word in a sentence or a key name, never a value.
- **Definitions**: one per client the recreate keeps calling; each `dependency.json` parses, its `name` equals its directory and `resource.name`, `resource.config` is non-empty, a `resource.contract` names an allowed file that exists beside it with `origin` `derived`, an `assumptions.md` sits beside it, and no `ref`, `suggestions`, `provenance` or `consumptionInstructions` appears; every Decision that names a resource has a definition directory of that name; `grep -rnE 'servers:|https?://' <out>/specs/design/dependencies` is empty.
- **Design files**: nothing under `<out>/specs/design/` but `domain-model.md` and `dependencies/`.
- **Bundle**: `scripts/bundle.mjs` ran without a refusal, and `unzip -l` on the result lists `manifest.json` and `specs/` paths only.

## Close

Report, in a few lines: the features by name, counts of stories, decisions,
P items, assumed lines, open questions and dependency definitions, the
files written, the bundle's path, and that the output is in `<out>`. Then
point at `references/handoff.md` for how the bundle enters an AEP project —
the order matters there, and this skill does not perform it.

Files in this skill

  • SKILL.md21.1 KB
  • references/bundle-format.md2.3 KB
  • references/dependency-shape.md2.8 KB
  • references/domain-model-shape.md610 B
  • references/handoff.md3.1 KB
  • references/prd-contract.md14 KB
  • references/registration-brief.md1.5 KB
  • scripts/bundle.mjs7 KB
  • scripts/sync-references.mjs4.8 KB
  • scripts/sync-references.test.mjs2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.