| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Model | sonnet |
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)"์ผ๋ก ์ด์ ๋ณธ๋ฌธ์ ๋ช ์ํ๊ณ , ๋ฉ์ถ์ง ์๊ณ ์งํํฉ๋๋ค โ ์๋ ํ์ดํ๋ผ์ธ์ด๋ฏ๋ก ์ฌ๋์๊ฒ ๋๋ฌป์ง ์์ต๋๋ค.
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
- ์๋ฌธ ํ์ฑ โ ์์ ํ ์ค์์ URL(ํผ๊ทธ๋ง ๋ฑ)์ด ์์ผ๋ฉด ๋ถ๋ฆฌํฉ๋๋ค.
- ๊ธฐ์กด ๋ช
์ธ ํ์ธ โ
docs/seed-spec-*.md์ค LOCKED ์ํ์ธ ๊ฒ ์์ผ๋ฉด ์ฐ์ ์ฐธ๊ณ ํฉ๋๋ค. - ๋์์ธ ์ปจํ ์คํธ ์์ง (ํผ๊ทธ๋ง ๋งํฌ๊ฐ ์์ ๋) โ ๋ ธ๋์ ์ค์ ํ๋ฉด ์์ยท์ํ๋ฅผ ์ฝ์ต๋๋ค.
-
PM ํ๋ ์ ์ ์ฉ โ FR/AC, ์ค์ฝํ In/Out, MoSCoW ์ฐ์ ์์, Fibonacci Story Point๋ก ์ ๋ฆฌํฉ๋๋ค(
cc-product:pmํ๋ ์์ํฌ ์ค์ฉ). - ๊ฒฐ๊ณผ ๋ฐํ โ 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.
- Requirements extraction: Turn the one-liner into Functional Requirements + Acceptance Criteria
- Scope boundary: Explicit in-scope / out-of-scope list
- Design grounding: When a Figma URL is present, pull real design states into the Acceptance Criteria instead of guessing
- Estimation: Fibonacci Story Point (1/3/5/8) and type/scope labels, per
cc-product:pm's tables - Seed-spec alignment: If a LOCKED
docs/seed-spec-*.mdexists for the domain, defer to it rather than re-deriving requirements
Input Parameters#
| Parameter | Required | Type | Description |
|---|---|---|---|
work_content | โ | string | Raw one-line work description passed to /dev:run |
initial_analysis | โ | object | Step 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 | โ | string | Figma URL extracted from work_content, if any |
locked_seed_spec | โ | string | null | Path 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 > 0Degradation Contract (GD-01, consistent with run.md Step 0)#
| Condition | Behavior |
|---|---|
| Figma MCP not installed | Skip design-context fetch, proceed text-only, warn (not a throw) |
| Figma URL unreachable / node not found | Same as above โ degrade + warn |
| Agent itself errors | Fall 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
AskUserQuestionor 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 theassumptionsarray / issue body โ do not silently guess without recording it. - This overrides the general
cc-product:pmskill'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 verbatimSkipped entirely#
--skip-pmpassed โ 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#
- Never block on a question โ this is an unattended pipeline step, not a chat persona
- Explicit options always win โ
--type/--scope/--pointpassed by the user are never second-guessed - LOCKED seed-spec is source of truth โ align with it instead of re-deriving requirements when one exists for the domain
- 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
- Record assumptions, don't hide them โ every non-obvious inference goes into the
assumptionsarray and the issue body, so a human reviewer can catch a wrong guess later