/sdlc:milestones-from-file
Generated from solutions/ontological/skills/milestones-from-file/SKILL.md.
Description
Section titled “Description”Given a filepath, do a best-effort extraction of milestone entities from
that file (markdown notes, planning docs, brain-dump, anything).
Creates one milestone file per extracted candidate via
sdlc milestone create, all inside a fresh worktree, then opens a PR.
Pass —auto to skip per-candidate confirmation.
Allowed tools
Section titled “Allowed tools”BashReadEditAskUserQuestion
Source
Section titled “Source”Usage:
/sdlc:milestones-from-file <path>— extract milestones interactively. The user gets to drop false positives before any files land./sdlc:milestones-from-file <path> --auto— extract and create every candidate without asking. Use when you trust the source file or when running unattended.
<path> may be absolute or relative to cwd. Any text/markdown file works; this skill makes no
assumption about format beyond “humans wrote planning intent in here somewhere.”
References:
${CLAUDE_PLUGIN_ROOT}entities/milestone/definition.md— storage path, id-as-filename convention,sdlc milestone createas canonical writer, validator usage, frontmatter ↔ body Deliverables sync rule. Read this once per project — it governs every skill that writes milestone files.
Project context (skill-specific):
- Ingest one file the user already wrote and propose milestones back from it. For interactively
authoring a single milestone by interview, use
/sdlc:milestone-new. - Use this when you already know the file. For a repo-wide scan that walks the whole project,
surfaces every planning-shaped file (READMEs, dated checklists, brain-dump notes), and produces
drafts for both tasks AND milestones, use
/sdlc:import-planning. - Fill body prose from the source (step 7) even under
--auto: the mechanicalsdlc milestone createrun leaves<...>placeholders in Goal / Success criteria / Out of scope / Risks / Tasks narrative; replace them with extracted content where the source supports it.
1. Parse args and resolve the source file
Section titled “1. Parse args and resolve the source file”Parse the invocation:
- First positional arg is the source filepath. Required. Error out clearly if missing.
--autoflag (anywhere) toggles silent bulk-create mode.
Resolve the source path to an absolute path. If it does not exist or is not a regular file, stop and report.
Compute the source stem in two explicit steps:
- Strip the file extension — take the basename and drop its final extension (the trailing
.<ext>), e.g.Q2-plan.txt→Q2-plan,Roadmap Notes.md→Roadmap Notes,archive.tar.gz→archive.tar. Do this yourself; do NOT rely onslugify()to strip the extension —slugify()only strips a trailing.md, so a non-.mdsource likeQ2-plan.txtwould otherwise keeptxtin the stem. - Normalize the extension-less basename to lowercase kebab-case: lowercase it, collapse
non-alphanumeric runs to
-, and trim leading/trailing-, with no length cap. This is exactly theslugify()/SLUG_REshape in${CLAUDE_PLUGIN_ROOT}lib/util/slug.ts; you may run the already-stripped basename throughslugify()for this part (it’s a no-op on the.mdstrip when the extension is already gone).
Examples: Roadmap Notes.md → roadmap-notes; Q2-plan.txt → q2-plan. The stem is used for the
worktree dir, branch name, and PR title.
If the stem ends up empty after sanitization, ask the user for one.
2. Pre-flight
Section titled “2. Pre-flight”Block and report (do not proceed) if any of these are true:
- A worktree already exists at
.sdlc/worktrees/milestones-from-<stem>(git worktree list). - A branch named
chore/milestones-from-<stem>already exists (git branch --list). - An open PR with that branch already exists (
gh pr list --head chore/milestones-from-<stem>).
If git status --porcelain on main shows uncommitted changes that would interfere with branching
from main, surface this to the user via AskUserQuestion: “Main has uncommitted changes. Proceed
(worktree branches from current main HEAD), or stop?” Default: stop.
3. Create the worktree
Section titled “3. Create the worktree”git worktree add .sdlc/worktrees/milestones-from-<stem> -b chore/milestones-from-<stem> mainFrom here on, use absolute paths under that worktree for all file operations. Do not cd the parent
session in.
4. Read the source file and extract candidates
Section titled “4. Read the source file and extract candidates”Read the full source file with the Read tool.
Then extract milestone candidates as a best-effort interpretive pass. A candidate is something that looks like “a planned outcome, often tied to a release” — a section header naming a phase, a bulleted release plan, a “Q3 goals” block, a roadmap row, etc.
For each candidate, capture frontmatter fields AND body prose. The frontmatter feeds step 6 (the script call). The body prose feeds step 7 (the Edit pass) — extracting it now means a single pass through the source rather than a re-read per candidate later.
Frontmatter fields (passed to sdlc milestone create)
Section titled “Frontmatter fields (passed to sdlc milestone create)”- title (required) — a one-line headline. Distill from the source heading or first sentence.
- status — default
open/draft. Upgrade toopen/plannedif the source says it’s committed but not started, oropen/activeif work has visibly begun. Useclosed/doneonly if the source explicitly states it shipped (then acompletion_noteis mandatory). Statuses, conditional requirements, and the full enum live in${CLAUDE_PLUGIN_ROOT}entities/milestone/schema.ts— consult it rather than guessing. - version — only if a semver-shaped version is named near the candidate.
- target_date — only if a YYYY-MM-DD-shaped date is named near the candidate.
- tags — short labels obvious from context (e.g.
frontend,docs). Skip if not obvious. - tasks — only if the source already names task basenames in valid
YYYY-MM-DD-<slug>form. Do NOT invent task names.
Body prose (applied in step 7)
Section titled “Body prose (applied in step 7)”For each candidate, capture whatever the source supports for each body section. These are best-effort distillations of source content, NOT inventions — quote, paraphrase, or summarize what’s there; leave the section unfilled if the source has nothing to say.
- Goal — one short paragraph on why this milestone exists / what outcome it pursues. Often distillable from the surrounding paragraph(s) around the candidate.
- Success criteria — bullets of observable outcomes the milestone produces. Pull from explicit “definition of done”, “we’ll know we’re done when”, or list-shaped goals; otherwise distill from the prose.
- Tasks narrative — for any
--taskbasenames captured above, write one bullet per task in the form- [[YYYY-MM-DD-slug]] — <why this task belongs to this milestone>. The<why>half is the interesting part — pull rationale from the source if the source says anything about each task, otherwise a brief one-liner. - Out of scope — bullets of things the source explicitly says are deferred / not in this milestone.
- Risks / open questions — bullets of concerns, open decisions, or dependencies the source mentions.
- Discovery context — one short paragraph on how this milestone came to be planned (the surrounding “we noticed X, so we want to do Y” framing in the source).
If the source supports none of these for a given candidate, that’s fine — step 7 will leave the placeholders intact and the author can flesh them out later. Carry over what the source actually says for every section it covers.
Filtering
Section titled “Filtering”Skip noise: section headers that are pure scaffolding (“Introduction”, “Table of Contents”), one-off
TODOs that aren’t milestones, items already obviously closed without enough context to write a
completion_note.
If extraction yields zero candidates, stop. Tear down the worktree (git worktree remove --force
then git branch -D the empty branch) and tell the user no milestones were found.
5. Confirm (unless —auto)
Section titled “5. Confirm (unless —auto)”If --auto was passed, skip this step.
Otherwise, present the candidate list to the user as a numbered text summary. For each candidate, include title, status, and any of version/target_date/tags/tasks that were populated. Keep each entry to ≤2 lines.
Then ask via AskUserQuestion how to proceed:
- Create all N (Recommended) — proceed with the full list.
- Drop some — user names which numbers to drop (plain-text follow-up).
- Stop — abort; tear down the worktree (
git worktree remove --force) and delete the branch (git branch -D chore/milestones-from-<stem>).
If the user drops candidates, confirm the reduced list back in text before continuing.
6. Create one milestone per confirmed candidate
Section titled “6. Create one milestone per confirmed candidate”For each confirmed candidate, run from inside the worktree:
${CLAUDE_PLUGIN_ROOT}cli/sdlc milestone create \ --project-root <worktree-absolute-path> \ --title "<title>" \ --status <status> \ [--version <semver>] \ [--target-date YYYY-MM-DD] \ [--tasks <basename>]... \ [--tags <tag>]...The op assigns ids serially as it runs (each invocation reads the current max id from the target dir), so running them sequentially in the same process is safe — do not parallelize. Capture each printed path.
If a single script invocation fails, stop and report the error verbatim. Do not roll back already-created files — the worktree isolates them from main, and the run continues or the PR is abandoned.
7. Fill in body prose from the source (applies in —auto too)
Section titled “7. Fill in body prose from the source (applies in —auto too)”The script produced files with <...> placeholder bodies. For each created file, walk the prose
captured in step 4 and use Edit to replace each placeholder block with the extracted content. The
body placeholders in the template (see ${CLAUDE_PLUGIN_ROOT}entities/milestone/body-template.eta)
are:
## Goal— placeholder paragraph in<...>## Success criteria— placeholder paragraph + two- <criterion>bullets## Deliverables— narrative description in<...>; the example bullet- [[YYYY-MM-DD-some-task]] — <why this task belongs to this milestone>was already replaced by the script if--taskflags were passed, but the descriptive<...>blurb above the bullets remains## Out of scope— placeholder paragraph + one- <item>bullet## Risks / open questions— placeholder paragraph + one- <risk>bullet## Discovery context— placeholder paragraph
Rules:
- Replace a placeholder block ONLY when step 4 captured content for that section. If a section has
no captured content, leave the
<...>placeholder so the author knows it’s still TODO. - When replacing, preserve the
## <Heading>line itself; only the placeholder body changes. - For bullet sections (Success criteria, Out of scope, Risks), replace both the
<...>descriptive blurb and the example- <...>bullet(s) with your captured bullets. - For the Tasks section: if step 4 captured per-task rationale that goes beyond the seed bullets
sdlc milestone createwrote, update those bullets in-place (same[[basename]]wikilink, richer— <why>half). If the source had nothing per-task, leave the op’s seed bullets as-is. - Do NOT touch the body
# <Title>line — the script already replaced it. - Do NOT add new sections the template doesn’t have.
- Do NOT carry over content that doesn’t fit any section (extra prose, side-comments). The author can add those by hand later.
Run this step in BOTH interactive and --auto mode.
8. Validate
Section titled “8. Validate”For each created path:
${CLAUDE_PLUGIN_ROOT}cli/sdlc entities validate <path>If any file fails validation, stop and report the error. Do not commit a partially-invalid batch. Running validation after the step 7 Edits catches a corrupted YAML front block.
9. Commit
Section titled “9. Commit”Stage only the new milestone files (use the paths captured in step 6; do not git add -A).
git -C <worktree-absolute-path> add docs/planning/milestones/M*.mdgit -C <worktree-absolute-path> commit -m "docs(planning): import N milestones from <source-stem>
Extracted from <source-file-path>:- M0001 — <title>- M0002 — <title>..."Substitute the actual ids and titles. Keep the bullet list complete — this is the durable record of what the batch contained.
10. Push and open the PR
Section titled “10. Push and open the PR”git -C <worktree-absolute-path> push -u origin chore/milestones-from-<stem>gh pr create --title "Import milestones from <source-stem>" --body "$(cat <<'EOF'## Summary
Best-effort extraction of milestone entities from <source-file-path>.N milestones created.
## Milestones
- M0001 — <title> (<status>)- M0002 — <title> (<status>)...
## Source
<source-file-path>
## Review notes
Each milestone is `open/draft` (or as labeled) and meant as a starting point.Body sections were best-effort distilled from the source; any remaining`<...>` placeholders indicate sections the source didn't cover.Reviewers should refine titles, fill in remaining placeholders, and adjuststatus before merging if any candidate isn't actually a milestone.EOF)"Capture and return the PR URL.
11. Report
Section titled “11. Report”Tell the user:
- The PR URL.
- The N milestone ids and titles created (one line each).
- The worktree path (in case they want to push follow-up edits before merging).
- Reminder: this skill never edits the source file. Inbound back-references are the author’s job.
Acceptance criteria
Section titled “Acceptance criteria”- Worktree exists at
.sdlc/worktrees/milestones-from-<stem>, branchchore/milestones-from-<stem>. - N milestone files exist under the worktree at
docs/planning/milestones/M<NNNN>.md, all passingsdlc entities validate. - For each created file, every body section that the source supported has been filled in (Goal,
Success criteria, Tasks narrative, Out of scope, Risks, Discovery context). A file whose source
had nothing to say for a section retains that section’s
<...>placeholder; a file with NO sections filled is an extraction failure to investigate (the source almost certainly had some prose worth carrying over). - A single commit contains exactly those N files (no other changes).
- Branch is pushed; PR is open against the default branch.
- The PR URL was reported back to the user.
- The source file is byte-identical to its pre-skill state.
- The extraction is a judgment call by the model. Err on the side of including candidates when
invoked interactively (the user can drop them in step 5). Err on the side of excluding marginal
candidates under
--auto— there’s no human gate. sdlc milestone createassigns ids by scanningdocs/planning/milestones/for the highest existingM<NNNN>. Ids assigned on this branch collide with any milestone created onmainbetween branch creation and PR merge. If the PR can’t merge cleanly due to id collisions, the reviewer must rebase and re-run the op for the affected files (or hand-edit the ids); this skill does not auto-resolve that.- This skill does not tear down the worktree after opening the PR. Worktree cleanup happens after
the PR merges —
git worktree remove --forcethengit branch -d. sdlc entities validateis frontmatter-only — running it after the step 7 body Edits does not check body prose, but it does catch a YAML front block an Edit corrupted.- When the source file lives inside the project, there is no worktree-copy divergence concern: the source is always read by its resolved absolute path (step 1), never via the worktree, and is never written.
- Committing model-generated messages. See
${CLAUDE_PLUGIN_ROOT}conventions/commit-messages.md—mktemp+ quoted-heredoc +git commit -Fis the canonical pattern. (Past failure: bare-mhit zsh-glob hazards on conventional-commit parens and tempfile collisions under parallel sessions.)