Skip to main content

kind: Milestone

What it is

A Milestone is a named deliverable target inside a Project. Milestones group issues toward a single target date and progress state — the workspace UI uses them to render burn-down strips and “what’s blocking v1.0” rollups. Every milestone is owned by exactly one project; deleting the parent project cascades to its milestones. Milestones are workspace-scoped (via their project) and idempotent on metadata.slug within a workspace. The server table has no slug column, so on export the manifest synthesises a kebab-case slug from metadata.name for round-trip identity.

YAML schema

Field reference

Examples

Minimal example

Realistic example with every common field

FK reference — milestone alongside its parent project (same file)

The Project and Milestone documents may live in the same multi-doc YAML file. Apply runs Project (phase 3) before Milestone (phase 5) regardless of declaration order, so the parent project_id is always resolvable by the time the milestone Plan executes. (A --dir flag for walking a manifest directory is on the follow-up list but not yet shipped — for now collect every kind in one ----separated file.)

CLI reference

The milestone CLI is nested under crewship project because milestones are children of a project. There is no standalone crewship milestone root command.
crewship apply is the only path that resolves project_slugproject_id for you. The flat crewship project milestone create takes the resolved project id (or slug) positionally and accepts individual --name / --target-date / --status / --description flags rather than a YAML file. For multi-milestone bundles, reach for crewship apply.

REST endpoint mapping

The REST surface is asymmetric: The manifest layer hides the asymmetry — Plan resolves project_slugproject_id against GET /api/v1/projects before issuing the create, then switches to the flat path for updates.

Validation rules

  • metadata.name is required (server rejects empty name with HTTP 400).
  • metadata.slug is required and must be unique within the workspace.
  • spec.project_slug is required and must reference a Project that is either declared in the same bundle or already present on the server.
  • spec.target_date, if set, must parse as YYYY-MM-DD (e.g. 2026-06-15).
  • spec.status, if set, must be one of planned, active, completed.
Failed validation is reported per-document with the offending milestone slug in the error message so multi-doc bundles surface every issue in one pass.

Apply behavior

ApplyUpsert (default)

  1. Look up the parent project ID via GET /api/v1/projects (filter client-side by slug — the API has no ?slug= parameter).
  2. List the project’s milestones via GET /api/v1/projects/{projectId}/milestones.
  3. Match by metadata.name:
    • No match → Action=Create, POST /api/v1/projects/{projectId}/milestones with {name, description, target_date, status}.
    • Match with differing fields → Action=Update, sparse PATCH /api/v1/milestones/{id} covering only the drifted fields.
    • Match with identical fields → Action=Unchanged, no HTTP call issued.

ApplyStrict

Fails with a slug already exists error if any declared milestone already exists by name in its parent project. Useful for new-environment bootstrapping where overwriting a same-named milestone would be a bug.

ApplyReplace

The milestone kind does not implement a distinct replace path today: MilestoneDocument.Plan only ever emits Create / Update / Unchanged (there is no PlanReplace, and the apply loop calls Plan regardless of mode). So even under --replace a declared milestone is upserted, not delete-then-recreated, and milestones the manifest no longer declares are not pruned. To reset a milestone’s identity, delete it explicitly (crewship project milestone delete <id>) before re-applying.

Round-trip via export

crewship export workspace walks every project, lists its milestones, and emits one kind: Milestone document per row. The export resolves project_id back to project_slug so the output is directly re-applyable into a different workspace or instance:
The synthesised slug strips non-alphanumeric runs to single dashes (v1.0 launchv1-0-launch). If two milestones in the same workspace share a name (legal at the DB level — uniqueness is per project), their slugs will collide and crewship apply will reject the bundle. Edit one of the slugs before re-applying. crewship export crew <slug> does NOT include milestones — milestones are workspace-scoped, not crew-scoped. Use crewship export workspace to capture them.

See also

  • kind: Project — parent record; must exist before any milestone.
  • kind: Label — applied to issues, not directly to milestones.
  • kind: SavedViewentity_type: issue views can filter by milestone via the UI (no manifest field today).
  • SPEC-2 section 3 — authoritative contract for this kind.