pm-spec-agent — 이슈 만들기 전, PM 관점으로 요구사항 정리하기#
| 항목 | 내용 |
|---|---|
| 모델 | sonnet |
한마디로#
"이런 거 만들어줘" 한 줄(+ 있으면 피그마 링크)을 받아서, PM이 검토하듯 "무엇을, 어디까지, 어떤 기준으로 완료됐다고 볼지"를 정리해 주는 도구입니다. ZenHub 이슈가 만들어지기 직전에 끼어들어, 얕은 키워드 추측 대신 실제 기획서 수준의 내용으로 이슈 본문을 채웁니다.
누가·언제 쓰나요#
/cc-dev:run이 새 이슈를 만들기 직전(Step 1.5)에 자동으로 호출합니다.- 사람이 직접 부르는 일은 거의 없습니다 —
/cc-dev:run파이프라인 내부 전용입니다. - 이 단계를 건너뛰려면
/cc-dev:run --skip-pm "긴급 수정"처럼 옵션을 붙입니다.
무엇을 해주나요#
- 작업 한 줄을 실제 기능 요구사항(FR)과 완료 기준(Acceptance Criteria)으로 풀어씁니다.
- 피그마 링크가 있으면 디자인을 읽어 화면에 실제로 있는 상태·항목을 요구사항에 반영합니다.
- 스코프(포함/제외)를 명시적으로 나눠, 나중에 "이것도 되나요?" 식의 재작업을 줄입니다.
-
Story Point(1/3/5/8)와 타입/스코프 라벨을 다시 매깁니다 — Step 1의 키워드 추측보다 정확합니다. 단, 사용자가
--type/--scope/--point를 직접 지정했다면 그 값을 그대로 존중합니다. - 이미 확정(LOCKED)된 기획 명세(
docs/seed-spec-*.md)가 있으면, 새로 지어내지 않고 그 문서와 정합성을 맞춥니다. -
화면 작업이면, 기획서·시안에 답이 없는 디자인 판단(빈 화면 문구·실패 알림 방식·입력 자릿수 등)을 여기서 먼저 결정해 완료 기준에 넣습니다 — 근거를 찾아 정하고 결정 기록(DDR)을 남깁니다(
cc-designer). 구현 단계에서 같은 질문을 다시 만나지 않게 하려는 것입니다. - 진짜 애매해서 판단이 안 서는 지점은 "가정(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 /cc-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 - Design decisions (screen requests): Settle AC-blocking design judgments the spec and mock left open, via
cc-designer:design-decision— recorded as DDRs, not as assumptions (Phase 4.5)
Input Parameters#
| Parameter | Required | Type | Description |
|---|---|---|---|
work_content | ✅ | string | Raw one-line work description passed to /cc-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; // 계약 블록 — 파이프라인이 파싱하는 것만 (AC·범위·DoD). 아래 템플릿 참조
narrative: string; // 아티팩트 원고 — 사람만 읽는 서술 (Summary·설계·DDR 전문). 비어 있을 수 있음
// ⚠️ body 와 narrative 를 하나로 합치지 말 것 — 어디를 옮길지
// 판정할 수 없게 된다 (SoT: rules/zenhub-conventions.md
// → Issue Body Artifact Contract)
assumptions: string[]; // explicit assumptions made where input was ambiguous
// — non-design only; design judgments become designDecisions
designDecisions?: DdrRecord[]; // settled design judgments (screen requests) — see Phase 4.5
figmaContext?: { url: string; nodeId?: string; summary: string };
seedSpecRef?: string; // docs/seed-spec-*.md path if used as source of truth
}
interface DdrRecord { // cc-designer:design-decision 의 DDR 포맷 (그 스킬이 SoT)
id: string; // " DDR-01 " — scope 누적 번호
decision: string; // 명령형 현재의 결정문 한 줄
rung: " R1 " | " R2 " | " R3 " | " R4 " | " R5 " ; // 근거를 준 사다리 칸 (R0 은 결정이 아니므로 제외)
evidence: string; // 사실 (선례 개수 · 수치 · 스킬명) — 취향 서술 금지
rejected?: string; // 기각한 대안 + 이유
reversibility: string; // 되돌리는 방법/비용
confidence: " high " | " medium " | " low " ; // low → ⚠️ 재검토 플래그
}body template (계약 블록 — 이슈 본문에 남는 것)#
파이프라인이 파싱하는 것만 담는다. run.md 이슈 생성 단계가 여기에 ## 📄 상세 기획 링크 블록을 앞에 붙인다.
## Functional Requirements
FR-1: {requirement}
Acceptance Criteria:
- {criterion}
- {criterion}
## Scope
### In scope
- {item}
### Out of scope
- {item}
## Design Decisions
- **DDR-01** {결정문} — 근거 {R번호} {신뢰도 low 면 ⚠️ 재검토}
## Assumptions
- {assumption made where input was ambiguous}narrative template (아티팩트 원고 — 사람만 읽는 것)#
## Summary
{one paragraph — what and why}
## Design Reference
- Figma: {url} (node {nodeId})
- Key states covered: {list}
## Design Decisions (근거 상세)
- **DDR-01** {결정문}
- 근거 {R번호}: {사실}
- 기각한 대안: {대안 + 이유}
- 되돌리기: {방법/비용}
## 화면·상태 설계
{표 · 상태 전이 · 와이어프레임 설명}Omit "## Design Reference" when no Figma URL was given, "## Design Decisions" when no design judgment had to be settled, and "## Assumptions" when the request had no genuine ambiguity.
DDR 은 두 곳에 나뉘어 실린다 — 결정문 한 줄은
body(계약), 근거·기각 대안·가역성 서술은narrative(아티팩트). 구현 단계는 결정문만 있으면 충분하고, 왜 그렇게 정했는지는 사람이 읽는다.
narrative가 20행 이하면 아티팩트를 발행하지 않는다 —run.md가 그대로body뒤에 붙여 종전 방식으로 생성한다 (SoT:rules/zenhub-conventions.md→ Issue Body Artifact Contract → 계층별 적용). 짧은 서술에 링크를 두는 것은 오버헤드다.
AC 와 DDR 의 관계: 결정이 완료 기준을 규정하면 AC 로도 내려 쓴다 (DDR 은 근거·기각 대안·가역성을 담고, AC 는 검증 가능한 문장으로 남는다). 구현 단계가 같은 판단을 다시 하지 않게 하는 것이 목적이다.
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 4.5: Design decision resolution (only if screenTypes.length > 0)
- Collect AC-blocking design questions left unanswered by seed spec (R0) and Figma context (R1)
— empty/error/offline states, failure-notification style, input format & digits,
copy for empty & permission-denied, target size/contrast when the mock is silent
- Settle each via cc-designer `design-decision` (ladder R2 code precedent → R3 skills → R4 numbers → R5)
· R2 requires an exhaustive survey of same-judgment sites — never skip to R3
· escalation 4종(brand identity / legal-policy / irreversible / R0 conflict)만 예외:
권고안 1개 + 근거를 body 에 남기고 그 항목만 미결로 표시 (여기서도 AskUserQuestion 은 부르지 않는다
— 이 에이전트는 비대화형이다. 호출자인 /cc-dev:run · /cc-dev:go 가 확인 절차를 담당한다)
- Fold results into AC + designDecisions[]; a design judgment must NOT be filed as an assumption
↓
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 /cc-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/cc-dev:run, that guidance is replaced by "log the assumption and proceed."
가정(Assumption) 과 결정(DDR) 은 다르다#
| Assumption | Design Decision (DDR) | |
|---|---|---|
| 무엇 | 입력이 없어 추측한 것 | 근거를 찾아 확정한 것 |
| 근거 | 없음 (그래서 "가정") | R2 선례 개수 / R4 수치 / R3 스킬명 |
| 검증 | 나중에 사람이 보고 틀렸으면 정정 | 근거를 반박해야 뒤집힘 |
| 어디에 | assumptions[] | designDecisions[] + AC |
디자인 판단을 assumptions 에 넣는 것은 회피다 — 선례·정량 기준으로 결정 가능한 것을 "가정"으로 위장하면
구현 단계가 같은 질문을 다시 만난다. 화면 요청에서 디자인 관련 항목이 assumptions 로 흘러가면 Phase 4.5 를 건너뛴 신호다.
Integration Scenario#
Called from /cc-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/cc-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 - Decide design questions, don't assume them — a judgment that code precedent or a quantitative standard can settle is a decision (DDR), not an assumption; filing it as an assumption pushes the same question into implementation