agentleFS
Yesterday-AIYesterday-AI/ytstack/skills/plan-milestone/SKILL.md

plan-milestone

Skill2 starsChanged 30 days agoMITView on GitHub

Defines the next milestone (goal, exit criteria, size) and writes M###-CONTEXT.md and M###-ROADMAP.md. Use after init-project or adopt-brownfield, after a milestone closed, or when the user says plan the next milestone, new milestone, define the milestone. Not for slicing (slice-milestone) and not for unvalidated greenfield ideas (office-hours first).

6 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

7 rules. 0 are common, 1 is distinctive, and 6 are very specific to this project.

  • 0

    Common

    Most of the 4 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If HAS_YTSTACK is no:

    line 129
  2. Distinctive:

    STOP. Do not invoke any other skill automatically.

    line 133
  3. Very specific:

    If YTSTACK_NON_INTERACTIVE is true, parse goal from prompt context.

    line 137
  4. Very specific:

    If YTSTACK_NON_INTERACTIVE is true, auto-pick M (medium) and log.

    line 166
  5. Very specific:

    Use Write tool. Path: {YT_DIR}/{NEXT_MILESTONE}-CONTEXT.md.

    line 184
  6. Very specific:

    Use Edit tool on {YT_DIR}/STATE.md.

    line 274
  7. Very specific:

    Do NOT invoke ytstack:slice-milestone, ytstack:plan-task, or any other downstream skill automatically.

    line 310

“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

plan-milestone

Define the next milestone for a ytstack project: goal, exit criteria, rough size. Produces a milestone roadmap skeleton; slice-level detail comes from the next skill (slice-milestone).

Anti-Pattern: "This feature is too small for a milestone"

Every feature goes through this. A CSS tweak, a single bugfix, a config change -- all of them. "Small" features are where unexamined assumptions cause the most wasted work. The milestone can be minimal (one slice, two tasks), but you MUST define its goal, exit criteria, and rough shape before planning slices.

Checklist

You MUST create a TodoWrite task for each of these items and complete them in order:

  1. Run preamble -- detect ytstack state + next milestone number
  2. HARD-GATE: check ytstack initialized -- abort if HAS_YTSTACK=no
  3. Ask goal -- one-sentence outcome the milestone delivers
  4. Ask exit criteria -- how we know it's done (concrete signals)
  5. Ask rough size -- S / M / L (cascading from task-count estimate)
  6. Write M###-CONTEXT.md -- Q/A transcript locked as the milestone's source-of-truth
  7. Write M###-ROADMAP.md -- skeleton with placeholder slices based on size
  8. Update STATE.md -- new current_milestone, next action points to slice-milestone
  9. Report + return -- suggest running ytstack:slice-milestone next

Process Flow

digraph plan_milestone {
    "Run preamble" [shape=box];
    "HAS_YTSTACK?" [shape=diamond];
    "Abort: run init-project first" [shape=doublecircle];
    "Ask goal" [shape=box];
    "Ask exit criteria" [shape=box];
    "Ask size" [shape=box];
    "Write CONTEXT + ROADMAP" [shape=box];
    "Update STATE.md" [shape=box];
    "Report + return" [shape=doublecircle];

    "Run preamble" -> "HAS_YTSTACK?";
    "HAS_YTSTACK?" -> "Abort: run init-project first" [label="no"];
    "HAS_YTSTACK?" -> "Ask goal" [label="yes"];
    "Ask goal" -> "Ask exit criteria";
    "Ask exit criteria" -> "Ask size";
    "Ask size" -> "Write CONTEXT + ROADMAP";
    "Write CONTEXT + ROADMAP" -> "Update STATE.md";
    "Update STATE.md" -> "Report + return";
}

Preamble

# ytstack resolver (M016): checkout = where this shell runs, via the shared lib.
_LIB="${CLAUDE_SKILL_DIR}/../../hooks/lib/git-ctx.sh"
if [ -f "$_LIB" ]; then
  . "$_LIB"; yt_git_ctx "$PWD"; yt_resolve_yt_dir >/dev/null
  _PROJECT_DIR="$YT_TOPLEVEL"; _YT_DIR="$YT_DIR"; _YT_SCOPE="$YT_SCOPE"; _IS_GIT="$YT_IS_GIT"; _PROJECT_KEY="$YT_REPO_KEY"
else
  # Host did not substitute CLAUDE_SKILL_DIR (e.g. Codex adapter): project scope only.
  _IS_GIT=$([ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ] && echo yes || echo no)
  _PROJECT_DIR=$(git rev-parse --show-toplevel 2>/dev/null || pwd -P)
  _YT_DIR=""; _YT_SCOPE=none; _PROJECT_KEY=""
  [ -d "$_PROJECT_DIR/.ytstack" ] && { _YT_DIR="$_PROJECT_DIR/.ytstack"; _YT_SCOPE=project; }
fi
cd "$_PROJECT_DIR" 2>/dev/null || true
_PROJECT_SLUG=$(basename "$_PROJECT_DIR")
_BRANCH=$(git branch --show-current 2>/dev/null || echo unknown)

_HAS_YTSTACK=$([ -n "$_YT_DIR" ] && echo yes || echo no)

# Next milestone number
_NEXT_MILESTONE="M001"
if [ -n "$_YT_DIR" ]; then
  _MAX=$(ls "$_YT_DIR"/M[0-9][0-9][0-9]-*.md 2>/dev/null | sed -E 's/.*M([0-9]{3})-.*/\1/' | sort -u | tail -1)
  if [ -n "$_MAX" ]; then
    _NEXT_MILESTONE=$(printf "M%03d" $((10#$_MAX + 1)))
  fi
fi

# Project-level frontmatter
_PROJECT_NAME=""
if [ -f "$_YT_DIR/PROJECT.md" ]; then
  _PROJECT_NAME=$(sed -n 's/^name: *//p' "$_YT_DIR/PROJECT.md" | head -1)
fi
# External issue tracker (STATE.md `tracker:`; none | linear | github-projects | paperclip | jira)
_TRACKER=none
if [ -f "$_YT_DIR/STATE.md" ]; then
  _TRACKER=$(sed -n 's/^tracker: *//p' "$_YT_DIR/STATE.md" | head -1)
  _TRACKER=${_TRACKER:-none}
fi

_NON_INTERACTIVE=false
if [ -n "$YTSTACK_NON_INTERACTIVE" ] || [ -n "$OPENCLAW_SESSION" ] || [ -n "$CLAUDE_AGENT_TEAM_MEMBER" ]; then
  _NON_INTERACTIVE=true
fi

echo "BRANCH: $_BRANCH"
echo "PROJECT_SLUG: $_PROJECT_SLUG"
echo "PROJECT_NAME: $_PROJECT_NAME"
echo "YT_DIR: $_YT_DIR"
echo "YT_SCOPE: $_YT_SCOPE"
echo "HAS_YTSTACK: $_HAS_YTSTACK"
echo "NEXT_MILESTONE: $_NEXT_MILESTONE"
echo "TRACKER: $_TRACKER"
echo "YTSTACK_NON_INTERACTIVE: $_NON_INTERACTIVE"

Procedure

Step 2: HARD-GATE

If HAS_YTSTACK is no:

"ytstack isn't initialized in this project yet. Run /ytstack:init-project first, then return here to plan the first milestone."

STOP. Do not invoke any other skill automatically.

Step 3: Ask goal

If YTSTACK_NON_INTERACTIVE is true, parse goal from prompt context. Otherwise use AskUserQuestion (open-ended):

Planning milestone {NEXT_MILESTONE} for project {PROJECT_NAME} on branch {BRANCH}.

What's the goal of this milestone? One sentence describing the outcome it delivers. Not the tasks -- the result.

{Only if TRACKER is not none:} Day-to-day issues and bugfixes are tracked in {TRACKER} and do not need a milestone; name a discrete initiative here, not the BAU queue.

Good examples:

  • "Users can sign up with email and get a verification link."
  • "Deploy a staging environment that mirrors prod config."
  • "Ship a CLI subcommand that imports CSV exports."

Remember as _GOAL.

Step 4: Ask exit criteria

Use AskUserQuestion (open-ended):

How will we know this milestone is done? List the concrete signals.

Concrete > abstract. "Signup form posts to /api/signup and returns 201" beats "signup works".

Give 2-4 checkable items, one per line.

Remember as _EXIT_CRITERIA (preserve line breaks).

Step 5: Ask rough size

If YTSTACK_NON_INTERACTIVE is true, auto-pick M (medium) and log. Otherwise use AskUserQuestion:

Rough size of this milestone? Use your gut -- we'll refine in slice-milestone.

Size drives the initial slice skeleton. You can always add slices later.

RECOMMENDATION: Choose M for most milestones. S is for near-trivial features. L signals you should probably split into two milestones; review after slicing.

A) S -- small. 1 slice, 2-4 tasks total. (Completeness: 10/10 for truly small work, risk: 8/10 if the scope is actually larger than it feels) B) M -- medium. 2-3 slices, 5-10 tasks total. (Completeness: 10/10, recommended for most milestones) C) L -- large. 4-5 slices, 11-20 tasks total. (Completeness: 10/10, risk: consider splitting into two milestones)

(to change later: edit size: in {NEXT_MILESTONE}-CONTEXT.md and {NEXT_MILESTONE}-ROADMAP.md)

Remember as _SIZE (value: S, M, or L).

Step 6: Write M###-CONTEXT.md

Use Write tool. Path: {YT_DIR}/{NEXT_MILESTONE}-CONTEXT.md.

---
milestone: {NEXT_MILESTONE}
project: {PROJECT_NAME}
created: {ISO_TIMESTAMP}
size: {SIZE}
---

# {NEXT_MILESTONE} -- Context

## Goal

{GOAL}

## Exit criteria

{EXIT_CRITERIA}

## Size

{SIZE} -- see `{NEXT_MILESTONE}-ROADMAP.md` for slice breakdown.

## Decisions locked in discuss phase

(Append decisions here as they're made during slicing + execution. Format: "YYYY-MM-DD: decided X because Y.")

## Open questions

(Items to resolve before or during slicing. Close as decisions land in the list above.)

Step 7: Write M###-ROADMAP.md

Use Write tool. Path: {YT_DIR}/{NEXT_MILESTONE}-ROADMAP.md.

Slice-count based on size: S=1, M=2-3, L=4-5. For v0.1, always seed the lower bound (S=1, M=2, L=4). User can add more during slice-milestone.

Template (substitute slice placeholders based on size):

---
milestone: {NEXT_MILESTONE}
project: {PROJECT_NAME}
size: {SIZE}
created: {ISO_TIMESTAMP}
status: planned
total_slices: {SLICE_COUNT}
completed_slices: 0
---

# {NEXT_MILESTONE} Roadmap

**Goal:** {GOAL}

**Exit criteria:**
{EXIT_CRITERIA}

## Slices

Slice detail lives in per-slice `{NEXT_MILESTONE}-S##-PLAN.md` files, created by `ytstack:slice-milestone`.

- [ ] S01 -- (to be planned)
{#if SIZE == "M" or SIZE == "L"}
- [ ] S02 -- (to be planned)
- [ ] S03 -- (to be planned)
{/if}
{#if SIZE == "L"}
- [ ] S04 -- (to be planned)
{/if}

## Run order

Slices execute sequentially. After each slice, `ytstack:reassess-roadmap` checks if the plan still fits reality.

## How to update this file

- Flip slice checkbox `[ ]` → `[x]` when its tasks are all `summarize-task`-confirmed
- Update `completed_slices` count
- On milestone completion, flip `status: planned` → `status: done` and update global ROADMAP.md

Replace the {#if ...} blocks with actual lines based on _SIZE:

  • S → keep only S01
  • M → S01, S02, S03
  • L → S01, S02, S03, S04

Step 8: Update STATE.md

Use Edit tool on {YT_DIR}/STATE.md. Change:

  • current_milestone: <old value> → current_milestone: {NEXT_MILESTONE}
  • active_slice: <old> → active_slice: none
  • active_task: <old> → active_task: none
  • last_updated: <old> → last_updated: {ISO_TIMESTAMP}

Also update the # State body:

  • Status line → **Status:** {NEXT_MILESTONE} planned ({SIZE}). Ready to slice.
  • Next action → Run \ytstack:slice-milestone` to break {NEXT_MILESTONE} into concrete slices and tasks.`

Step 9: Report + return

Report:

Milestone {NEXT_MILESTONE} planned ({SIZE}):

  • Goal: {GOAL}
  • Exit criteria: {EXIT_CRITERIA_FIRST_LINE} ...
  • Slices: {SLICE_COUNT} placeholder(s) in {NEXT_MILESTONE}-ROADMAP.md

Files created:

  • {YT_DIR}/{NEXT_MILESTONE}-CONTEXT.md -- Q/A locked
  • {YT_DIR}/{NEXT_MILESTONE}-ROADMAP.md -- slice skeleton

STATE.md updated: current_milestone = {NEXT_MILESTONE}.

Next step: run /ytstack:slice-milestone to define the slices in detail.

Terminal State

The terminal state is one of:

  • Returning control after writing M###-CONTEXT.md + M###-ROADMAP.md + updating STATE.md
  • Aborting if HAS_YTSTACK=no (with clear message to run init-project first)

Do NOT invoke ytstack:slice-milestone, ytstack:plan-task, or any other downstream skill automatically. The ONLY valid next actions are:

  • User reviews the generated files
  • User runs /ytstack:slice-milestone themselves when ready

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.