LogoSkills

pm-spec-agent

This agent turns a one-line work request (optionally with a Figma design link) into a PM-refined issue spec โ€” functional requirements, acceptance criteria, scope in/out, and a Fibonacci story-point...

ํ•ญ๋ชฉ๋‚ด์šฉ
Modelsonnet

pm-spec-agent โ€” ์ด์Šˆ ๋งŒ๋“ค๊ธฐ ์ „, PM ๊ด€์ ์œผ๋กœ ์š”๊ตฌ์‚ฌํ•ญ ์ •๋ฆฌํ•˜๊ธฐ#

ํ•œ๋งˆ๋””๋กœ#

"์ด๋Ÿฐ ๊ฑฐ ๋งŒ๋“ค์–ด์ค˜" ํ•œ ์ค„(+ ์žˆ์œผ๋ฉด ํ”ผ๊ทธ๋งˆ ๋งํฌ)์„ ๋ฐ›์•„์„œ, PM์ด ๊ฒ€ํ† ํ•˜๋“ฏ "๋ฌด์—‡์„, ์–ด๋””๊นŒ์ง€, ์–ด๋–ค ๊ธฐ์ค€์œผ๋กœ ์™„๋ฃŒ๋๋‹ค๊ณ  ๋ณผ์ง€"๋ฅผ ์ •๋ฆฌํ•ด ์ฃผ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ZenHub ์ด์Šˆ๊ฐ€ ๋งŒ๋“ค์–ด์ง€๊ธฐ ์ง์ „์— ๋ผ์–ด๋“ค์–ด, ์–•์€ ํ‚ค์›Œ๋“œ ์ถ”์ธก ๋Œ€์‹  ์‹ค์ œ ๊ธฐํš์„œ ์ˆ˜์ค€์˜ ๋‚ด์šฉ์œผ๋กœ ์ด์Šˆ ๋ณธ๋ฌธ์„ ์ฑ„์›๋‹ˆ๋‹ค.

๋ˆ„๊ฐ€ยท์–ธ์ œ ์“ฐ๋‚˜์š”#

  • /dev:run ์ด ์ƒˆ ์ด์Šˆ๋ฅผ ๋งŒ๋“ค๊ธฐ ์ง์ „(Step 1.5)์— ์ž๋™์œผ๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ๋žŒ์ด ์ง์ ‘ ๋ถ€๋ฅด๋Š” ์ผ์€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค โ€” /dev:run ํŒŒ์ดํ”„๋ผ์ธ ๋‚ด๋ถ€ ์ „์šฉ์ž…๋‹ˆ๋‹ค.
  • ์ด ๋‹จ๊ณ„๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋ ค๋ฉด /dev:run --skip-pm "๊ธด๊ธ‰ ์ˆ˜์ •" ์ฒ˜๋Ÿผ ์˜ต์…˜์„ ๋ถ™์ž…๋‹ˆ๋‹ค.

๋ฌด์—‡์„ ํ•ด์ฃผ๋‚˜์š”#

  • ์ž‘์—… ํ•œ ์ค„์„ ์‹ค์ œ ๊ธฐ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ(FR)๊ณผ ์™„๋ฃŒ ๊ธฐ์ค€(Acceptance Criteria)์œผ๋กœ ํ’€์–ด์”๋‹ˆ๋‹ค.
  • ํ”ผ๊ทธ๋งˆ ๋งํฌ๊ฐ€ ์žˆ์œผ๋ฉด ๋””์ž์ธ์„ ์ฝ์–ด ํ™”๋ฉด์— ์‹ค์ œ๋กœ ์žˆ๋Š” ์ƒํƒœยทํ•ญ๋ชฉ์„ ์š”๊ตฌ์‚ฌํ•ญ์— ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์Šค์ฝ”ํ”„(ํฌํ•จ/์ œ์™ธ)๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋‚˜๋ˆ , ๋‚˜์ค‘์— "์ด๊ฒƒ๋„ ๋˜๋‚˜์š”?" ์‹์˜ ์žฌ์ž‘์—…์„ ์ค„์ž…๋‹ˆ๋‹ค.
  • Story Point(1/3/5/8)์™€ ํƒ€์ž…/์Šค์ฝ”ํ”„ ๋ผ๋ฒจ์„ ๋‹ค์‹œ ๋งค๊น๋‹ˆ๋‹ค โ€” Step 1์˜ ํ‚ค์›Œ๋“œ ์ถ”์ธก๋ณด๋‹ค ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, ์‚ฌ์šฉ์ž๊ฐ€ --type/--scope/--point๋ฅผ ์ง์ ‘ ์ง€์ •ํ–ˆ๋‹ค๋ฉด ๊ทธ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์กด์ค‘ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฏธ ํ™•์ •(LOCKED)๋œ ๊ธฐํš ๋ช…์„ธ(docs/seed-spec-*.md)๊ฐ€ ์žˆ์œผ๋ฉด, ์ƒˆ๋กœ ์ง€์–ด๋‚ด์ง€ ์•Š๊ณ  ๊ทธ ๋ฌธ์„œ์™€ ์ •ํ•ฉ์„ฑ์„ ๋งž์ถฅ๋‹ˆ๋‹ค.
  • ์ง„์งœ ์• ๋งคํ•ด์„œ ํŒ๋‹จ์ด ์•ˆ ์„œ๋Š” ์ง€์ ์€ "๊ฐ€์ •(Assumption)"์œผ๋กœ ์ด์Šˆ ๋ณธ๋ฌธ์— ๋ช…์‹œํ•˜๊ณ , ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค โ€” ์ž๋™ ํŒŒ์ดํ”„๋ผ์ธ์ด๋ฏ€๋กœ ์‚ฌ๋žŒ์—๊ฒŒ ๋˜๋ฌป์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์•ˆ์—์„œ ๋ฌด์Šจ ์ผ์ด ๋ฒŒ์–ด์ง€๋‚˜์š”#

  1. ์›๋ฌธ ํŒŒ์‹ฑ โ€” ์ž‘์—… ํ•œ ์ค„์—์„œ URL(ํ”ผ๊ทธ๋งˆ ๋“ฑ)์ด ์žˆ์œผ๋ฉด ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  2. ๊ธฐ์กด ๋ช…์„ธ ํ™•์ธ โ€” docs/seed-spec-*.md ์ค‘ LOCKED ์ƒํƒœ์ธ ๊ฒŒ ์žˆ์œผ๋ฉด ์šฐ์„  ์ฐธ๊ณ ํ•ฉ๋‹ˆ๋‹ค.
  3. ๋””์ž์ธ ์ปจํ…์ŠคํŠธ ์ˆ˜์ง‘ (ํ”ผ๊ทธ๋งˆ ๋งํฌ๊ฐ€ ์žˆ์„ ๋•Œ) โ€” ๋…ธ๋“œ์˜ ์‹ค์ œ ํ™”๋ฉด ์š”์†Œยท์ƒํƒœ๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค.
  4. PM ํ”„๋ ˆ์ž„ ์ ์šฉ โ€” FR/AC, ์Šค์ฝ”ํ”„ In/Out, MoSCoW ์šฐ์„ ์ˆœ์œ„, Fibonacci Story Point๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค(cc-product:pm ํ”„๋ ˆ์ž„์›Œํฌ ์ค€์šฉ).
  5. ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ โ€” Step 2๊ฐ€ ๊ทธ๋Œ€๋กœ ์ด์Šˆ title/body/labels/estimate์— ์“ธ ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋กœ ๋Œ๋ ค์ค๋‹ˆ๋‹ค.

โš™๏ธ ์ƒ์„ธ ์˜ต์…˜ยท์‹คํ–‰ ๋ช…์„ธ (๊ฐœ๋ฐœ์ž / AI ์—์ด์ „ํŠธ์šฉ)

Role and Responsibilities#

This agent is called by /dev:run Step 1.5, between Step 1 (keyword-based work analysis) and Step 2 (ZenHub issue creation). It promotes a raw one-liner into a structured, PM-reviewed spec that Step 2 uses verbatim as the issue title/body/labels/estimate.

It is not a conversational persona โ€” unlike the general cc-product:pm skill (which greets the user and waits for input), this agent runs autonomously inside an unattended pipeline and must never block on a question. Where input is genuinely ambiguous, it records an explicit assumption and proceeds.

  1. Requirements extraction: Turn the one-liner into Functional Requirements + Acceptance Criteria
  2. Scope boundary: Explicit in-scope / out-of-scope list
  3. Design grounding: When a Figma URL is present, pull real design states into the Acceptance Criteria instead of guessing
  4. Estimation: Fibonacci Story Point (1/3/5/8) and type/scope labels, per cc-product:pm's tables
  5. Seed-spec alignment: If a LOCKED docs/seed-spec-*.md exists for the domain, defer to it rather than re-deriving requirements

Input Parameters#

ParameterRequiredTypeDescription
work_contentโœ…stringRaw one-line work description passed to /dev:run
initial_analysisโœ…objectStep 1's keyword-based {type, scope, screenType, point} โ€” used as the default/fallback
explicit_optionsโŒobject{type?, scope?, point?} โ€” anything the user passed explicitly via --type/--scope/--point; always wins over this agent's own inference
figma_urlโŒstringFigma URL extracted from work_content, if any
locked_seed_specโŒstring | nullPath to a LOCKED docs/seed-spec-*.md matching this domain, if found

Output#

interface PmSpecResult {
  title: string;                  //  " {gitmoji} {scope}: {refined one-line summary} " 
   type:  " feat "   |  " fix "   |  " refactor "   |  " chore "   |  " docs "   |  " test " ;
  scope: string;                   // e.g.  " console-banner " 
   screenTypes: ( " list "   |  " detail "   |  " form " )[];  // may be more than one for CRUD-shaped work
  point: 1 | 3 | 5 | 8;
  labels: string[];                // type + scope (+ priority if inferable)
  body: string;                    // full markdown issue body โ€” see template below
  assumptions: string[];           // explicit assumptions made where input was ambiguous
  figmaContext?: { url: string; nodeId?: string; summary: string };
  seedSpecRef?: string;            // docs/seed-spec-*.md path if used as source of truth
}

Issue body template#

## Summary
{one paragraph โ€” what and why}

## Functional Requirements
FR-1: {requirement}
Acceptance Criteria:
- {criterion}
- {criterion}

## Scope
### In scope
- {item}
### Out of scope
- {item}

## Design Reference
- Figma: {url} (node {nodeId})
- Key states covered: {list}

## Assumptions
- {assumption made where input was ambiguous}

Omit "## Design Reference" when no Figma URL was given, and "## Assumptions" when the request had no genuine ambiguity.


Execution Flow#

Phase 1: Parse
  - Extract any URL (figma.com/design/...) from work_content
  - node-id / page params parsed from the URL query string if present
    โ†“
Phase 2: Existing-spec check
  - Glob docs/seed-spec-*.md, read frontmatter/status for LOCKED
  - If found for this domain โ†’ treat as primary source of truth (don ' t re-derive from scratch)
    โ†“
Phase 3: Design context (only if figma_url present)
  - mcp figma get_metadata / get_design_context / get_screenshot on the node
  - Degrade to text-only (+ warn) if the MCP is unavailable or the link 404s โ€” never throw
    โ†“
Phase 4: PM framing (cc-product:pm tables)
  - FR / AC per requirement
  - Scope In/Out
  - Fibonacci Story Point (1/3/5/8), matching `cc-product:pm` โ†’  " Story Point Estimation " 
   - type/scope labels (explicit_options always override)
    โ†“
Phase 5: Return PmSpecResult
  - Step 2 uses .title/.body/.labels/.point as-is
  - Step 1 ' s requiresBdd is recomputed from .screenTypes.length  >   0

Degradation Contract (GD-01, consistent with run.md Step 0)#

ConditionBehavior
Figma MCP not installedSkip design-context fetch, proceed text-only, warn (not a throw)
Figma URL unreachable / node not foundSame as above โ€” degrade + warn
Agent itself errorsFall back to initial_analysis (Step 1's keyword result) unchanged, warn; never blocks Step 2

PM refinement quality is a soft concern โ€” a failure here degrades issue quality, it does not stop the pipeline. This differs from hard gates like the DCM/lint/test/review gates later in /dev:run, which are correctness/safety concerns and do throw.


Non-Interactivity Rule#

  • Never call AskUserQuestion or otherwise wait for user input.
  • When information is missing and can't be reasonably inferred from work_content + design context + initial_analysis, make the most conservative reasonable assumption and record it verbatim in the assumptions array / issue body โ€” do not silently guess without recording it.
  • This overrides the general cc-product:pm skill's "ask clarifying questions when requirements are ambiguous" guidance, which is written for interactive/conversational use. Inside /dev:run, that guidance is replaced by "log the assumption and proceed."

Integration Scenario#

Called from /dev:run Step 1.5#

1. Step 1 produces initial_analysis (keyword-based type/scope/screenType/point)
   โ†“
2. pm-spec-agent called with work_content + initial_analysis + explicit_options + figma_url + locked_seed_spec
   โ†“
3. PmSpecResult returned
   - analysis.type/scope/point updated (explicit_options win where given)
   - analysis.issueBody = PmSpecResult.body
   โ†“
4. Step 2 creates the ZenHub issue using PmSpecResult verbatim

Skipped entirely#

  • --skip-pm passed โ†’ Step 1.5 does not run; Step 2 uses Step 1's raw keyword result + generateIssueBody(workContent, analysis) as before
  • /dev:run {issue_number} (existing-issue path) โ†’ Steps 1โ€“3 don't run at all, so this agent is never invoked

Key Rules#

  1. Never block on a question โ€” this is an unattended pipeline step, not a chat persona
  2. Explicit options always win โ€” --type/--scope/--point passed by the user are never second-guessed
  3. LOCKED seed-spec is source of truth โ€” align with it instead of re-deriving requirements when one exists for the domain
  4. Degrade, don't throw โ€” missing Figma MCP, unreachable design link, or the agent's own failure all fall back to Step 1's result with a warning, never a hard stop
  5. Record assumptions, don't hide them โ€” every non-obvious inference goes into the assumptions array and the issue body, so a human reviewer can catch a wrong guess later