Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
_See `meta/roadmap-0.4.md` for the full list of what's planned for this release and the
status of each item. Entries land here one PR at a time as each ships._

- **Rollout posture (Step 1.9)** — in a codebase that gates new behavior behind feature flags
(or a beta cohort, a canary), "is this flagged?" was a decision no pass asked, so it got
invented mid-ticket or skipped. New grounding step, same shape as design posture: record
once per project whether a rollout mechanism exists (`none` / `flags` / `staged`) and where
its convention lives (`CLAUDE.md`-first). When it does: Pass 1 requires a per-feature
*Rollout:* line (flag, default, audience — or "not flagged, because"); Pass 3 marks each
ticket that ships behind the flag 🚩 (soft signal, like 🎨) and, for a temporary flag,
**adds a cleanup ticket to the DAG** blocked by the last flagged one — removing a flag is
work, and work gets a node. Ticket detail and Fork A issue descriptions carry a one-line
*Rollout:* field. Persists as `rollout_posture` / `rollout_convention_location`; unset →
`none`. See `llm-coding-workflow.md`'s "Rollout posture" section.

## 0.3.0 — 2026-08-05

- **Design posture (Step 1.8)** — dstack previously had no place to affirmatively decide how
Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ produced one of them.
existing design system/component library govern it, is it deliberately utility-only, or
does one need to be established now before Pass 2 goes far? In the reference project,
`design_posture: existing` — the host app's `CLAUDE.md` already documents its component
library, so no new design work was needed, just a pointer to it.
library, so no new design work was needed, just a pointer to it. Last, the same question
about **rollout** (Step 1.9): does this codebase ship new behavior behind feature flags (or
a beta cohort, a canary), and if so where's the convention? The reference project answers
`rollout_posture: none` — the digest is opt-in per user, so no flag. A project that answers
`flags` gets a required *Rollout:* line per feature in Pass 1, a 🚩 marker on each ticket
that ships behind the flag, and — if the flag is temporary — a cleanup ticket in the DAG.
3. **Pass 1 — Intake:** guided questions fill in whatever Step 1.6 didn't already answer — the
"what": product intent, problem, MVP surface, success criteria. Anything that can't be
settled yet becomes a row in the **open questions ledger** — an id, a status (resolved with
Expand Down Expand Up @@ -205,6 +210,15 @@ Any line only on one side of that diff is a word whose count changed — worth c
- **No visual surface at all (CLI, API, background job) → "none."** Also a deliberate, recorded
answer, not a skipped question.

**What rollout posture do I pick (Step 1.9)?**
- **The codebase has a feature-flag system → "flags."** Point dstack at the convention (or
write it into `CLAUDE.md` now — flag service, naming, add/toggle/remove, cleanup
expectation). From then on every feature says whether it's flagged, flagged tickets get 🚩,
and a temporary flag's removal is a ticket in the DAG, not a reminder.
- **Beta cohorts, per-tenant enables, canaries → "staged."** Same shape, different mechanism.
- **Everything ships to everyone → "none."** The recommended default when nothing says
otherwise — and a recorded answer, so a later reader knows it was considered.

## Common mistakes

Drawn from real evidence, not hypotheticals — these are documented failure modes a prior,
Expand Down Expand Up @@ -244,7 +258,7 @@ start and otherwise trusted. Downstream commands will pick up the new value on t
predates the four prep questions, or one where only some got backfilled)? Every command falls
back to that question's stated recommended default from Step 1.5 rather than treating it as an
error: `team_shape` → `solo`, `risk_tolerance` → `gate-every-ticket`, `resumability_cadence` →
`same-day` (skip the recap), `retro_cadence` → `per-phase`. This is a deliberate default, not a
`same-day` (skip the recap), `retro_cadence` → `per-phase`, `rollout_posture` → `none`. This is a deliberate default, not a
bug — see "Migrating a repo that already had dstack" above for backfilling it properly instead
of relying on the fallback indefinitely.

Expand Down
3 changes: 3 additions & 0 deletions examples/reference-project/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ than one line per ticket here, it belongs in the ticket file instead.
barrel through it
- 🎨 design-touching — check against the project's design ground rules (`notes.md` Step 1.8)
before closing; not a hard gate, just a visible reminder
- 🚩 flag-gated — this ticket puts behavior behind a feature flag; confirm it's wired and
defaults per the project's rollout convention (`notes.md` Step 1.9) before closing. Not used
in this project (`rollout_posture: none`) — listed so the legend is complete

## Dependency graph

Expand Down
8 changes: 8 additions & 0 deletions examples/reference-project/notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ retro_cadence: per-phase
repo_profile_location: mixed
design_posture: existing
design_ground_rules_location: claude-md
rollout_posture: none
rollout_convention_location: n/a
last_session_at: 2026-06-14T09:00:00-06:00
---

Expand Down Expand Up @@ -36,6 +38,12 @@ longer and more specific than this, but the structure and section names are the
components directly, and are marked 🎨 in `TODO.md` as a reminder to check that reuse against
`CLAUDE.md`'s conventions before either ticket is closed — not because this project needed a
bespoke visual identity.
- **Step 1.9 — rollout posture:** `rollout_posture: none` (front matter above). The host app
has no feature-flag system, and the digest is opt-in per user anyway — the preference toggle
*is* the rollout gate, so no flag, no 🚩 tickets, no cleanup node. Recorded so a later reader
knows this was decided, not skipped. A project whose posture is `flags` would instead carry
a *Rollout:* line under MVP surface, 🚩 on each flagged ticket, and a cleanup ticket in the
DAG.

---

Expand Down
17 changes: 12 additions & 5 deletions harnesses/claude-code/commands/dstack-ticket.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ points for this command:
project uses rather than assuming Linear. Do not ask the user which fork — detect it from
the files present.
- Prep-question answers (`team_shape`, `risk_tolerance`, `resumability_cadence`,
`retro_cadence`) live in the project's `notes.md` front matter — read them; they shape how
this command presents its pick (see Steps 4–5).
`retro_cadence`) and the grounding postures (`design_posture`, `rollout_posture`) live in
the project's `notes.md` front matter — read them; they shape how this command presents
its pick (see Steps 4–6).

## 2. Pick the project

Expand Down Expand Up @@ -68,8 +69,9 @@ Inside the project folder, check what's actually there:
- **`TODO.md` present → Fork B (local).** This is the default for solo/lighter-weight
projects in this repo. Read `TODO.md` directly:
- The **Tickets** section — one line per ticket (skeleton only; full detail lives in
`tickets/<id>.md`), checkbox (`[ ]`/`[~]`/`[x]`/`[!]`, plus 🚧 for human-gated infra steps
and 🎨 for tickets that touch user-visible UI), with `blocked-by` / `blocks`.
`tickets/<id>.md`), checkbox (`[ ]`/`[~]`/`[x]`/`[!]`, plus 🚧 for human-gated infra steps,
🎨 for tickets that touch user-visible UI, and 🚩 for tickets that put behavior behind a
feature flag), with `blocked-by` / `blocks`.
- The **DAG** section — the diagram, declared **roots**, and **Phases** table.
- Checkboxes are a **hint, not ground truth** — status drifts. Before treating an upstream
ticket as done, sanity-check against the **code on disk** (does the file/dependency/route
Expand Down Expand Up @@ -115,7 +117,12 @@ Present your choice to the user with:
- **The ticket**: id (`KAI-####` for Fork A, or the local id like `V1` for Fork B), title,
and its phase. Call out a 🚧 human-gate if present, and a 🎨 marker if present — note that its
micro-plan and close-out should check the work against the project's design ground rules
(Step 1.8 in `SKILL.md`) before it's called done.
(Step 1.8 in `SKILL.md`) before it's called done. Call out a 🚩 marker the same way: read
the ticket's **Rollout:** line (flag name, default, cleanup ticket) and note that the
micro-plan should wire the flag per the convention `rollout_convention_location` points at
(Step 1.9 in `SKILL.md`), and that close-out confirms it defaults the way the line says.
If the ticket *is* the cleanup ticket, say so — its job is removing the flag and the dead
path, nothing more.
- **Why it's next**: which upstream deps are satisfied, what it unblocks downstream.
- **Alternatives**: any other tickets on the eligible frontier (especially parallelizable
branches). If `team_shape` is `small-team` or `larger-team`, frame these explicitly as things
Expand Down
12 changes: 10 additions & 2 deletions harnesses/claude-code/commands/dstack-yolo.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ architectural context and the prep-question front matter). If exactly one projec

Parse from `TODO.md`:
- the **ticket set** with statuses (`[ ]` open · `[~]` in-progress · `[x]` done · `[!]`
blocked/paused · 🚧 human-gate · 🎨 design-touching) — one line per ticket, pointing at
`tickets/<id>.md` for depth,
blocked/paused · 🚧 human-gate · 🎨 design-touching · 🚩 flag-gated) — one line per ticket,
pointing at `tickets/<id>.md` for depth,
- the **dependency edges** (`Blocked-by` / `Blocks`), **roots**, and **topological phases**,
- the last ~10 lines of the **Execution log** inline; the full history lives in
`execution-log-archive.md` if more context is needed.
Expand Down Expand Up @@ -119,6 +119,14 @@ If this ticket is marked 🎨, check the work against the project's design groun
autonomous execution — but a ticket that visibly drifts from established ground rules should be
noted in its re-spec, the same as any other divergence, not silently shipped.

If this ticket is marked 🚩, confirm before `[x]` that the new behavior is actually behind
the flag named in the ticket's **Rollout:** line, that the flag is registered per the
convention `rollout_convention_location` points at (Step 1.9 in `SKILL.md`), and that it
defaults the way the line says (usually off). Same soft-gate semantics as 🎨: a mismatch is a
re-spec note, not a silent ship — and if the flag turned out to be unnecessary or permanent,
the Rollout line and the cleanup ticket (drop it from the DAG, with a log entry) get updated
in the same commit.

If the implementation diverged from the plan, **rewrite `tickets/<id>.md`** so it describes
what was actually built (not the original promise) — this is the atomic bundling that makes
re-spec reliable; do it as part of closing the ticket, not as a follow-up. Flip the ticket to
Expand Down
64 changes: 57 additions & 7 deletions harnesses/claude-code/skill/dstack/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ Once the project is settled, look inside it for an existing living doc (`notes.m
and/or a `TODO.md`.
- **If found:** name what you found and which pass it reached (scan its headers — does it
have a Pass 2 / Pass 3 section? a ticket DAG? front matter with prep-question answers, see
Step 1.5? a `design_posture` answer, see Step 1.8?). Ask whether to **resume** that project
(and at which pass) or **start something new**. If resuming, load it into context, skip
Step 1.5 and Step 1.8 (their answers are already in the doc's front matter), and jump to the
Step 1.5? a `design_posture` answer, see Step 1.8? a `rollout_posture` answer, see Step
1.9?). Ask whether to **resume** that project (and at which pass) or **start something
new**. If resuming, load it into context, skip Steps 1.5, 1.8, and 1.9 (their answers are
already in the doc's front matter), and jump to the
relevant step below. Before treating the doc as settled, run the **open questions ledger's
required confirmation pass** (see Step 2) over any row still carrying a "Resolved" or
"Deferred" status from a prior session — a resumed session is exactly the kind of consuming
Expand Down Expand Up @@ -84,6 +85,8 @@ retro_cadence: per-phase | close-out-only
repo_profile_location: claude-md | dstack-file | mixed | none
design_posture: none | utility | existing | new
design_ground_rules_location: claude-md | design-md | dstack-file | n/a
rollout_posture: none | flags | staged
rollout_convention_location: claude-md | dstack-file | n/a
---
```

Expand Down Expand Up @@ -123,7 +126,7 @@ If material is provided:
silently.

If nothing is provided, this step is a no-op — proceed to Step 1.7, then Step 1.8, then Step
2's Pass 1 as normal.
1.9, then Step 2's Pass 1 as normal.

## Step 1.7 — Repo profile (existing codebases only)

Expand Down Expand Up @@ -186,15 +189,50 @@ replace Pass 2's per-feature visual decisions — once ground rules exist (or th
utility/none decision is recorded), Pass 2's Component architecture still makes this feature's
specific screens/components, grounded against whatever was established here.

## Step 1.9 — Rollout posture (does new behavior ship behind a flag?)

Skip this step when resuming (the project's earlier answer persists in front matter as
`rollout_posture`). Otherwise, ask once, via `AskUserQuestion` — see `llm-coding-workflow.md`'s
"Rollout posture" section for the full reasoning:

1. **No rollout mechanism** *(recommended default when nothing in `CLAUDE.md` or the codebase
says otherwise)* — changes ship to everyone. Record `rollout_posture: none`; this is a
deliberate answer, not a skipped question. Nothing below applies to this project.
2. **Feature flags** — a flag system governs new behavior. Ask where its **convention** lives:
which flag service/library, how flags are named, how one is added/toggled/removed, and
whether temporary flags are expected to be cleaned up. Same `CLAUDE.md`-first logic as
Step 1.7: if it's documented there, point at it; if it's partially or not documented, ask
whether to **add the missing pieces to `CLAUDE.md`** *(recommended)* or write them to
`doc/dstack/<project>/rollout-convention.md`. Record `rollout_posture: flags`.
3. **Other staged rollout** — a beta cohort, per-tenant enable, canary, or similar. Same
convention question and recording. Record `rollout_posture: staged`.

Record the answer and its location in front matter (`rollout_posture`,
`rollout_convention_location`). When the posture is `flags` or `staged`, three things follow
downstream — say so now, briefly, so the question doesn't feel arbitrary:

- **Pass 1 states the per-feature decision** (Step 2): a required *Rollout:* line under the
MVP surface — behind which flag, defaulting to what, visible to whom — or *not flagged,
because …*. Unknown yet → an open-questions ledger row, not silence.
- **Pass 3 marks flagged tickets 🚩 and adds a cleanup ticket** (Step 3) when the flag is
temporary — removing a flag is work, and work gets a DAG node.
- **Ticket detail carries a `Rollout:` field** — flag name, default, cleanup ticket id — in
`tickets/<id>.md` for Fork B and in the Linear issue description for Fork A, so whoever
picks the ticket up (or reads the issue) sees the flag guidance without opening the doc.

## Step 2 — Establish the project + entry pass

If starting fresh, ask for a short **project name** (kebab-case) → this becomes
`doc/dstack/<project>/`. Then confirm **which pass we're entering** — if Step 1.6 assessed
provided material, put its recommendation to the user as the default option rather than asking
blind; otherwise ask directly:
- **Pass 1 — Intake:** the *what*. Product intent, the problem, the MVP surface, success
criteria. Output: `doc/dstack/<project>/notes.md` with the Pass-1 sections (plus the Step 1.5
front matter). Anything that can't be settled yet goes into an **open questions ledger**
criteria. If `rollout_posture` is `flags` or `staged` (Step 1.9), the MVP surface ends with
a required **Rollout:** line — *behind flag `<name>`, default off, visible to <who>* or *not
flagged, because <reason>* — decided here, not invented mid-ticket; if it genuinely can't
be decided yet, it's a ledger row. Output: `doc/dstack/<project>/notes.md` with the Pass-1
sections (plus the Step 1.5 front matter). Anything that can't be settled yet goes into an
**open questions ledger**
section — a table with columns id / question / raised-in-pass / status — not a loose bullet
list. See `llm-coding-workflow.md`'s "Open questions" section for the exact row format.
- **Pass 2 — Architecture:** rewrite the *same* doc through a structural lens (data model,
Expand Down Expand Up @@ -265,7 +303,8 @@ scan/triage mechanics.

### Fork B — Local TODO.md (lighter weight / solo / autonomous) *(default for this repo today)*
- Generate **`doc/dstack/<project>/TODO.md`** as a pure **skeleton**: a status legend
(`[ ] [~] [x] [!]`, plus 🚧 human-gate and 🎨 design-touching), the DAG diagram, roots +
(`[ ] [~] [x] [!]`, plus 🚧 human-gate, 🎨 design-touching, and 🚩 flag-gated when the
project's posture calls for it), the DAG diagram, roots +
topological phases, and **one line per ticket** — `- [ ] **T3** Recipient mgmt — blocked-by:
T1,T3 · blocks: T6,T7 · phase 3 · [detail](tickets/T3.md)` — plus only the last ~10 lines of
the Execution log (older entries roll into `execution-log-archive.md`). Match the shape used
Expand All @@ -282,6 +321,14 @@ scan/triage mechanics.
`new` (Step 1.8) — unlike 🚧 this is **not** a hard gate, just a visible signal that the
ticket's Pass-4 micro-plan and close-out should check its work against the design ground
rules before the ticket is called done, so visual polish doesn't silently get skipped.
- Mark any ticket that puts behavior behind the flag with 🚩 when `rollout_posture` is
`flags` or `staged` (Step 1.9) — same soft-signal semantics as 🎨: the micro-plan and
close-out should confirm the flag is wired and defaults per the convention. Each 🚩
ticket's `tickets/<id>.md` (or Linear description, Fork A) gets a one-line **Rollout:**
field — flag name, default, cleanup ticket. **If the flag is temporary, add a cleanup
ticket to the DAG now** — remove the flag + the dead path, `blocked-by` the last 🚩 ticket,
in its own phase after launch — so removal is a node the next-ticket pick will find, not a
reminder that rots. If the flag is permanent, say so in the Rollout line instead.
- Hand off: tell the user to run **`/dstack-yolo`** to autonomously work the whole DAG
(findings scan → plan-gate per `risk_tolerance` → implement → verify → re-spec → loop), or
**`/dstack-ticket`** if they just want the next ticket picked and confirmed without
Expand All @@ -294,6 +341,9 @@ Summarize: the project, the pass reached, the artifacts written (`doc/dstack/<pr
and `TODO.md` + `tickets/` + `findings/` if Fork B). Tell the user the exact next command
(`/dstack-ticket` or `/dstack-yolo`). Remind them:

- if `rollout_posture` is `flags` or `staged`, every 🚩 ticket's close-out checks the flag
against the convention, and the cleanup ticket is in the DAG — it'll surface on the
frontier after launch, not need remembering;
- the **post-completion re-spec keystone** keeps tickets truthful, and it works best when it's
bundled into the same commit/close-out step as the code — not left as a thing to remember;
- **freestanding findings** (audits, reviews) get written to `findings/` and get scanned before
Expand Down
Loading