osdlc

import-planning

Walk a project root for planning-shaped prose (READMEs with roadmap sections, brain-dump notes, dated checklists, issue archives, etc.) and produce draft task/milestone entities under docs/planning/, so a project can adopt the SDLC plugin without re-keying its prior planning history. Spins up a fresh worktree, runs a deterministic scanner, dispatches one sub-agent per candidate to interpret it, presents a batched plan for one-shot approval, then shells out to `sdlc task create` / `sdlc milestone create` and Edits the drafted body sections in. Opens a PR at the end. Pass --auto to skip the confirmation prompt, --dry-run to stop at the plan, --scope <path> to restrict the scan.

osdlc 3 Updated 4d ago

Resources

1
GitHub

Install

npx skillscat add osdlc/osdlc/import-planning

Install via the SkillsCat registry.

SKILL.md

$sdlc:import-planning

Usage:

  • $sdlc:import-planning — full scan + per-candidate interpretation + batched approval + create
    entities + open PR.
  • $sdlc:import-planning --dry-run — same as above through the batched-plan render, then stop.
    Never creates worktree, branch, or entity files.
  • $sdlc:import-planning --auto — skip the per-candidate confirmation prompt and create every
    non-skip verdict.
  • $sdlc:import-planning --scope <subpath> — restrict the scan to a subdirectory under the project
    root. Repeatable.

Flags combine: $sdlc:import-planning --auto --scope docs --scope notes is valid.

Project context (don't re-derive every run):

  • The deterministic scanner is sdlc project scan candidates. It is a Bun-run CLI. Read-only.
    It walks the project, respects .gitignore, applies default excludes (docs/planning/, .git,
    node_modules, dist, build, target, __pycache__, .venv, .next, .claude, etc.),
    filters by extension (.md, .txt, .org), and applies cheap content heuristics. Output is
    JSON: {candidates: [{path, size, mtime, heuristic_hints: [...]}]}.
  • Entity creation is delegated. Never reimplement frontmatter/slug/date generation in this skill —
    shell out to sdlc task create and sdlc milestone create. Their --help is the contract.
  • Body prose fill is part of the job. The mechanical sdlc task create / sdlc milestone create
    run leaves <...> placeholders in the body sections; this skill replaces them with extracted
    content where the source supports it, including under --auto.
  • One PR per invocation. Two commits is fine (one per entity-type batch) if both batches are
    non-empty.
  • Entity-type scope: task and milestone only. Other types (bug/epic/feature) don't have schemas
    yet — sub-agents that want to nominate one of those should return entity_type: skip instead.

1. Parse args and pre-flight

Parse the invocation:

  • --dry-run (flag) — stop after the batched plan; create no worktree, no branch, no files.
  • --auto (flag) — skip the per-candidate confirmation prompt in Step 6.
  • --scope <subpath> (repeatable) — restrict the scan to subpaths under the project root.

Pre-flight on the main repo (the project root the user invoked from). Probe the four scaffolding
signals — a dirty working tree, a worktree-path collision, a branch-name collision, and an open PR
on the branch — with one report-only op:

scripts/sdlc project preflight-worktree import-planning \
  --branch chore/import-planning \
  --project-root .

The op probes <main-checkout>/.sdlc/worktrees/import-planning for a worktree collision,
chore/import-planning for a branch collision, and gh pr list --head chore/import-planning for an
open PR. It is report-only: it surfaces the signals (dirty + the raw porcelain lines,
worktreeCollision, branchCollision, openPrs, and the convenience anyCollision) and never
decides stop/proceed, never aborts, and never cleans up state. The stop/proceed and consent
decisions below are this skill's, not the op's. Parse the JSON (the adapter defaults to
--output json).

If dirty is true and the changes 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. Show the porcelain lines so the user sees exactly what is at
risk. Do NOT sweep their work into this skill's commits.

If anyCollision is true (worktreeCollision, branchCollision, or a non-empty openPrs), stop
and ask the user — there may be an unfinished prior run. Do not delete existing state without
confirmation.

2. Dry-run the scanner first (always — including --dry-run mode)

Before creating a worktree, surface what's about to be considered:

scripts/sdlc project scan candidates \
  [--scopes <subpath>]... \
  --project-root .

Forward every --scopes the user passed. Parse the JSON (the op defaults to --output json). The
candidate list is sorted by path for determinism.

  • If candidates is empty: print No planning-shaped content found. and exit. Do not create a
    worktree.
  • Otherwise proceed to Step 3.

3. Create the worktree

Skip this step entirely if --dry-run was passed — --dry-run means "tell me what you'd do, don't
touch the filesystem."

Otherwise:

scripts/sdlc project create-worktree import-planning --branch chore/import-planning --start-point main

The op prints the worktree's absolute path, under the main checkout; use that path.

From here on, use absolute paths under the worktree for every file operation. Do not cd the parent
session into the worktree.

4. Re-run the scanner inside the worktree

The worktree branched from main, so its tree is the same content the main-side scanner saw. Re-run
anyway from inside the worktree so the candidate paths are relative to the worktree root the rest of
the skill operates on:

scripts/sdlc project scan candidates \
  [--scopes <subpath>]... \
  --project-root <worktree-absolute-path>

If --dry-run was passed, this re-run is unnecessary — the Step 2 output is the plan input. Use it
directly.

Re-parse the JSON. Each candidate has path (relative to project root), size, mtime, and
heuristic_hints.

5. Dispatch one sub-agent per candidate (single message, parallel)

For every candidate, dispatch one sub-agent in a single message so they run concurrently. This
is the higher-level analysis layer — the scanner gathered cheap signals; the agents apply judgment.

Each sub-agent prompt must include:

  • The project root (the worktree absolute path, or the main repo absolute path in --dry-run mode).
  • The single candidate this agent owns: path, size, mtime, heuristic_hints.
  • Permission to read the candidate file and other read-only inspection (git log, peeking at
    nearby files for cross-reference). Sub-agents must not write or mutate state — entity creation
    is the orchestrator's job.
  • The verdict contract (below).

Per-candidate interpretation contract

Each sub-agent does, in order:

  1. Read the candidate file end-to-end.
  2. Decide entity type for what the source represents:
    • milestone — looks like a planned outcome tied to a release (semver, target date, success
      criteria, "Q3 goals" framing, release-row).
    • task — looks like a discrete piece of work (a bug to fix, a single feature increment, "next
      steps" with concrete ACs).
    • skip — not actually planning content (install instructions misclassified as roadmap,
      scaffolding TOC, contributor guide).
  3. Distill the frontmatter fields and body sections from the source. For tasks: headline, slug
    suggestion (kebab-case), Goal, Today, Proposed, Approach, Acceptance criteria, Out of scope,
    Dependencies, Discovery context. For milestones: title, Goal, Success criteria, Tasks narrative,
    Out of scope, Risks/open questions, Discovery context, plus any obvious frontmatter (version,
    target_date, tags). Carry over what the source actually says — quote, paraphrase, summarise.
    Don't invent ACs the source doesn't justify; leave a section as the original <...> placeholder
    if the source has nothing to say for it.
  4. Return a strict JSON object (no surrounding prose) so the orchestrator can aggregate cleanly:
{
  "path": "<the candidate path verbatim>",
  "entity_type": "task | milestone | skip",
  "headline": "<one-line headline; required when type is task or milestone>",
  "slug_suggestion": "<kebab-case slug; required when type is task>",
  "frontmatter": {
    "state": "<task state: planning/draft|planning/proposed|planning/backlog|open/ready  OR  milestone state: open/draft|open/planned|open/active>",
    "impact": "<task only: high|medium|low>",
    "complexity": "<task only: small|medium|large>",
    "version": "<milestone only, if a semver appears in the source>",
    "target_date": "<milestone only, YYYY-MM-DD, if a date appears in the source>",
    "tags": ["..."]
  },
  "body_sections": {
    "Goal": "<...>",
    "Today": "<task only>",
    "Proposed": "<task only>",
    "Approach": "<task only>",
    "Acceptance criteria": ["<AC-1>", "..."],
    "Out of scope": ["<item>", "..."],
    "Dependencies": ["<dep>", "..."],
    "Discovery context": "<...>",
    "Success criteria": ["<milestone only>"],
    "Tasks narrative": "<milestone only>",
    "Risks / open questions": ["<milestone only>"]
  },
  "evidence": ["<source quote or rationale>", "..."]
}

Omit body_sections keys the source doesn't support (do NOT pad with <...>; the orchestrator
preserves the template placeholder in that case).

Keep each agent's response under ~400 words; use general-purpose subagent type.

Single message — every Agent tool call goes in one assistant turn. Sequential dispatch defeats
the design.

Aggregate verdicts

When every sub-agent returns, merge their verdicts into a flat list. If any sub-agent failed to
return parseable JSON, treat that candidate as skip and surface the failure in the plan.

6. Build the batched plan and confirm

Render a single markdown plan for the user. Group strictly by entity_type:

# Import plan

Found <N> candidate(s) → would produce <T> task(s), <M> milestone(s), <S> skipped.

## Tasks (<T>)
For each `task` verdict:
- `<slug>` — <headline>
  - Source: `<path>` (<size> bytes, <heuristic_hints>)
  - State: <state> · Impact: <impact> · Complexity: <complexity>
  - Body coverage: Goal/Today/Proposed/Approach/AC<s>/Out-of-scope/Discovery — list which sections were filled vs left as placeholder.

## Milestones (<M>)
For each `milestone` verdict:
- `<title>` — <headline>
  - Source: `<path>` (<size> bytes, <heuristic_hints>)
  - State: <state> · Version: <version|->  · Target: <target_date|->
  - Body coverage: Goal/Success/Tasks/Out-of-scope/Risks/Discovery — list filled vs placeholder.

## Skipped (<S>)
For each `skip` verdict, one line: `<path>` — <one-sentence reason>.

If --dry-run was passed, stop here. Print the plan. Exit without creating worktree, branch, or
entity files. (If a worktree was somehow created in Step 3 despite --dry-run, that's a bug — Step
3 must be skipped under --dry-run.)

If --auto was passed, skip the confirmation prompt and proceed to Step 7 with the full
non-skip set.

Otherwise, ask the user via AskUserQuestion with these options:

  • Run the plan as shown (Recommended if it looks right) — proceed to Step 7 with the full
    non-skip set.

  • Walk one-by-one first — drives a follow-up AskUserQuestion per task/milestone candidate
    (create, drop, or change type to task|milestone|skip), then re-builds the plan with the
    answers folded in and asks again.

  • Cancel — don't create anything — tear down the worktree and branch this skill created, then
    exit:

    scripts/sdlc project teardown-worktree import-planning \
      --branch chore/import-planning \
      --project-root <worktree-parent-repo-root>

    The op removes only the named <main-checkout>/.sdlc/worktrees/import-planning worktree and
    deletes only chore/import-planning — it never sweeps unrelated state, and it is idempotent (a
    missing worktree or branch is reported, not an error). It honors the global --dry-run flag:
    under --dry-run it probes and reports intent only, making no git/filesystem writes.

7. Create entities and fill bodies

For each non-skip candidate, in order:

7a. For task candidates

From inside the worktree:

scripts/sdlc task create \
  --project-root <worktree-absolute-path> \
  --headline "<headline>" \
  --state <state> \
  --impact <impact> \
  --complexity <complexity> \
  [--tags <tag>]... \
  [--related <slug-or-basename>]...

The slug positional is omitted on purpose: task create derives the slug from --headline
(P-0001, the shared capped deriveSlug), so the sub-agent's synthesized headline is the single
source of truth and slug_suggestion does not have to be restated here. (An explicit <slug>
positional still wins if you ever need to override the derivation.) If you want to SHOW or confirm
the would-be slug + id before creating — or check for an existing collision — call the read-only
companion first: sdlc task preview-id "<headline>" --project-root <worktree-absolute-path> (add
--output json for {slug, id, exists, exact_match, similar, warning}). It derives the same slug
create will, so a previewed slug is byte-identical to what gets minted.

Capture the printed path. If the op exits non-zero, stop and report the error verbatim. Do not roll
back already-created files; the worktree isolates them from main, and a half-done batch is
recoverable by either continuing or abandoning the PR.

7b. For milestone candidates

scripts/sdlc milestone create \
  --project-root <worktree-absolute-path> \
  --title "<headline>" \
  --state <state> \
  [--version <semver>] \
  [--target-date YYYY-MM-DD] \
  [--tasks <basename>]... \
  [--tags <tag>]...

sdlc milestone create assigns ids serially as it runs (M0001, M0002,...), so run milestone
creations sequentially in the same process — do not parallelize. Capture each printed path.

7c. Fill body sections (applies under --auto too)

Each created file has <...> placeholders in its body sections. For every created path, walk the
captured body_sections from the sub-agent's verdict and use Edit to replace each placeholder block
with the extracted content.

Rules:

  • Replace a placeholder block ONLY when the verdict supplied content for that section. Sections the
    verdict left empty stay as the original <...> placeholder.
  • Preserve the ## <Heading> line itself; only the placeholder body changes.
  • For bullet sections (Acceptance criteria, Out of scope, Success criteria, Risks/open questions),
    replace both the descriptive <...> blurb AND the example - <...> bullet(s) with the verdict's
    bullets.
  • Do NOT touch the # <Title> line — the create ops in 7a / 7b already replaced it.
  • Do NOT add sections the templates don't have.
  • Do NOT carry over source content that doesn't fit any section. The author can add it by hand
    later.
  • Fill every section the source supports. A file with no sections filled is an extraction failure,
    not a valid output.

7d. Validate

For every created path:

scripts/sdlc entities validate <path>

If any file fails validation, stop and report. Do not commit a partially-invalid batch.

8. Commit, push, open PR

Stage only the created entity files (paths captured in 7a / 7b). Do not git add -A. The narrow
git add is required — sdlc commit create commits the staged index; it does NOT narrow scope.

git add docs/planning/tasks/*.md docs/planning/milestones/M*.md
scripts/sdlc commit create --message - <<'EOF'
docs(planning): import <T> task(s) and <M> milestone(s)

Imported from prior planning content in this repo via $sdlc:import-planning.

Tasks:
- <basename> — <headline>
- ...

Milestones:
- M<NNNN> — <title>
- ...
EOF

The message reaches git through the op's mktemp'd file (the - sentinel: stdin bytes the shell
does not re-parse), so the conventional-commit parens are safe. See
${CLAUDE_PLUGIN_ROOT}/conventions/commit-messages.md.

One commit covering both batches is fine. Split into two commits (one per entity type) if T and M
are both non-zero and the diff reads more naturally that way.

git push -u origin chore/import-planning
scripts/sdlc pr create --base main --head chore/import-planning \
  --title "Import planning content as tasks/milestones" --body-file <body-file>

PR body shape:

## Summary

Best-effort extraction of planning content from this repo into SDLC
task/milestone entities. T tasks, M milestones. S candidates skipped.

## Tasks created (<T>)

- `<basename>` — <headline> (<state>)
  - Source: `<path>`

## Milestones created (<M>)

- M<NNNN> — <title> (<state>)
  - Source: `<path>`

## Skipped (<S>)

- `<path>` — <reason>

## How this was generated

Invoked `$sdlc:import-planning`. The skill ran
`sdlc project scan candidates`, dispatched one sub-agent per candidate
(in a single message — parallel), aggregated verdicts, then shelled
out to `sdlc task create` / `sdlc milestone create` and Edited the
drafted body sections in.

## Review notes

Body sections were best-effort distilled from the source. Any
remaining `<...>` placeholders indicate sections the source didn't
cover. Reviewers should refine headlines, fill remaining placeholders,
and adjust state before merging if any imported entity isn't actually
the type the skill inferred.

Return the PR URL to the user.

9. Report

Tell the user:

  • The PR URL.
  • The T task basenames + headlines and M milestone ids + titles, one line each.
  • The S skipped paths and their reasons.
  • The worktree path (in case they want to push follow-up edits before merging).
  • Reminder: this skill never edits the source files. Adding inbound back-references from the source
    docs to the new entities is the author's job.

Acceptance criteria

  • Worktree exists at <main-checkout>/.sdlc/worktrees/import-planning, branch
    chore/import-planning (skipped under --dry-run).
  • T+M entity files exist under the worktree at docs/planning/tasks/ and
    docs/planning/milestones/, all passing sdlc entities validate.
  • For each created file, every body section the source supported has been filled in. 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.
  • A single commit (or two, split by type) contains exactly those files (no other changes).
  • Branch is pushed; PR is open against the default branch.
  • The PR URL was reported back to the user.

Failure modes

  • Sub-agent returned non-JSON — treat that candidate as skip and note the failure in the plan.
    Don't block the batch on one flaky agent.
  • sdlc task create / sdlc milestone create failure — stop and surface stderr. The worktree
    isolates the partial state; user can re-run or abandon.
  • Validator failure on a freshly-created file — Edit likely corrupted the frontmatter. Stop,
    report the offending file's frontmatter back to the user, do not commit.
  • No candidates found — exit cleanly after Step 2; do not create a worktree.

Notes

  • Read-only until Step 7. The scanner is read-only. Sub-agents are read-only (they may shell
    git log, but never git add / write). Mutation begins only after the user approves the plan (or
    --auto skipped the prompt).
  • Parallelism comes from one message. Step 5 dispatches every Agent call in a single response.
  • Type scope is task and milestone only. Bug/epic/feature don't have schemas yet. The sub-agent
    contract enforces this — anything else gets returned as skip.
  • No de-duplication. This skill is additive. If a source file was already imported in a prior
    run, re-importing creates a second entity — the user can drop it. Out of scope (per the task spec)
    to compare against docs/planning/ for collisions.
  • Eval suite. tests/run_evals.test.ts (in this skill's source directory; dev-only, not shipped
    in the built plugin) validates the scanner against fixtures (mixed, nothing-planning,
    milestone-vs-task, gitignore-respect). Run it before changing the project scan candidates
    scanner.
  • Worktree teardown is deferred. This skill does not tear down the worktree after opening the
    PR. Cleanup happens after the PR merges; $sdlc:project-cleanup will surface the merged branch
    and its worktree.
  • Committing model-generated messages. Author the multi-line commit (Step 8) with sdlc commit create --message - over a quoted heredoc — the message reaches git through the op's mktemp'd
    file, so the body's parens never hit a zsh-glob under a bare -m. See
    ${CLAUDE_PLUGIN_ROOT}/conventions/commit-messages.md.
  • Branch naming. This skill's worktree branch uses chore/import-planning-<stem>, not
    task/<task-basename>. See ${CLAUDE_PLUGIN_ROOT}/conventions/branch-naming.md.