pm-skill 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +17 -0
- package/AGENTS.md +87 -0
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/SKILL.md +134 -0
- package/config.yml +82 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.js +144 -0
- package/dist/env.d.ts +31 -0
- package/dist/env.js +153 -0
- package/dist/linear.d.ts +48 -0
- package/dist/linear.js +138 -0
- package/dist/notion.d.ts +34 -0
- package/dist/notion.js +225 -0
- package/dist/workflows.d.ts +2 -0
- package/dist/workflows.js +405 -0
- package/package.json +61 -0
package/.env.example
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# === Linear ===
|
|
2
|
+
# Settings > API > Personal API Keys 에서 발급
|
|
3
|
+
LINEAR_API_KEY=lin_api_xxxxxxxx
|
|
4
|
+
|
|
5
|
+
# setup 커맨드로 확인 가능
|
|
6
|
+
LINEAR_DEFAULT_TEAM_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
7
|
+
LINEAR_DEFAULT_PROJECT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
8
|
+
|
|
9
|
+
# === Notion ===
|
|
10
|
+
# https://www.notion.so/my-integrations 에서 발급
|
|
11
|
+
NOTION_API_KEY=secret_xxxxxxxx
|
|
12
|
+
|
|
13
|
+
# Notion에서 문서를 생성할 상위 페이지 ID
|
|
14
|
+
NOTION_ROOT_PAGE_ID=xxxxxxxx
|
|
15
|
+
|
|
16
|
+
# (선택) 버그 추적 DB ID — 설정 시 report-bug가 DB 엔트리로 생성
|
|
17
|
+
NOTION_BUG_DB_ID=xxxxxxxx
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# PM Skill — Agent Instructions
|
|
2
|
+
|
|
3
|
+
This file provides instructions for AI coding assistants (Codex, Claude Code, etc.) to use the pm-skill CLI.
|
|
4
|
+
|
|
5
|
+
## What is pm-skill?
|
|
6
|
+
|
|
7
|
+
A structured project management CLI that integrates Linear (issue tracking) and Notion (documentation). It enforces a config-driven workflow where only pre-defined labels, templates, and severity levels are allowed.
|
|
8
|
+
|
|
9
|
+
## How to Run Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# If installed globally via npm:
|
|
13
|
+
pm-skill <command> [args] [flags]
|
|
14
|
+
|
|
15
|
+
# If running from source:
|
|
16
|
+
npx tsx src/workflows.ts <command> [args] [flags]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Available Commands
|
|
20
|
+
|
|
21
|
+
### setup
|
|
22
|
+
Verify Linear/Notion connection and show team/label configuration.
|
|
23
|
+
```bash
|
|
24
|
+
pm-skill setup
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### start-feature
|
|
28
|
+
Create a Linear issue + Notion PRD page with bidirectional links.
|
|
29
|
+
```bash
|
|
30
|
+
pm-skill start-feature "<title>"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### report-bug
|
|
34
|
+
File a bug report with severity-based priority mapping.
|
|
35
|
+
```bash
|
|
36
|
+
pm-skill report-bug "<title>" --severity <urgent|high|medium|low>
|
|
37
|
+
```
|
|
38
|
+
Default severity: medium.
|
|
39
|
+
|
|
40
|
+
### add-task
|
|
41
|
+
Add a sub-issue to a parent issue.
|
|
42
|
+
```bash
|
|
43
|
+
pm-skill add-task <parent-issue-id> "<title>"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### relate
|
|
47
|
+
Set a relationship between two issues.
|
|
48
|
+
```bash
|
|
49
|
+
pm-skill relate <issue1> <issue2> --type <related|similar>
|
|
50
|
+
```
|
|
51
|
+
Default type: related.
|
|
52
|
+
|
|
53
|
+
### block
|
|
54
|
+
Set a blocking dependency (issue1 blocks issue2).
|
|
55
|
+
```bash
|
|
56
|
+
pm-skill block <blocker-issue> <blocked-issue>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### attach-doc
|
|
60
|
+
Attach a document URL to an issue with type validation.
|
|
61
|
+
```bash
|
|
62
|
+
pm-skill attach-doc <issue> --url "<url>" --title "<title>" --type <source-of-truth|issue-tracking|domain-knowledge>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### get
|
|
66
|
+
Show issue details including children, relations, and attachments.
|
|
67
|
+
```bash
|
|
68
|
+
pm-skill get <issue-id>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
- **`.env`** — API keys and IDs. Looked up in: CWD → `~/.pm-skill/` → package root.
|
|
74
|
+
- **`config.yml`** — Labels, templates, priorities, severity mappings. Same lookup order.
|
|
75
|
+
|
|
76
|
+
## Workflow Patterns
|
|
77
|
+
|
|
78
|
+
### Feature Development
|
|
79
|
+
1. `start-feature "Feature Name"` — creates Linear issue + Notion PRD
|
|
80
|
+
2. `add-task ENG-XX "Sub-task 1"` — break down into sub-tasks
|
|
81
|
+
3. `relate ENG-XX ENG-YY` — link related issues
|
|
82
|
+
4. `attach-doc ENG-XX --url "..." --title "..." --type source-of-truth`
|
|
83
|
+
|
|
84
|
+
### Bug Fix
|
|
85
|
+
1. `report-bug "Bug Description" --severity high`
|
|
86
|
+
2. `add-task ENG-XX "Root cause analysis"`
|
|
87
|
+
3. `add-task ENG-XX "Fix and test"`
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lsm
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# pm-skill
|
|
2
|
+
|
|
3
|
+
Structured project management CLI that integrates **Linear** and **Notion**. Designed to work with AI coding assistants — Claude Code, Codex, and any tool that can run shell commands.
|
|
4
|
+
|
|
5
|
+
> "Design freedom, usage discipline" — only labels, templates, and severity levels defined in `config.yml` are allowed.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **start-feature** — Create Linear issue + Notion PRD page, auto-linked
|
|
10
|
+
- **report-bug** — File bug with severity-based priority mapping
|
|
11
|
+
- **add-task** — Add sub-issues to a parent
|
|
12
|
+
- **relate / block** — Set issue relationships and dependencies
|
|
13
|
+
- **attach-doc** — Attach documents with type validation
|
|
14
|
+
- **get** — View issue details with children, relations, and attachments
|
|
15
|
+
- **setup** — Verify connections and discover team/label IDs
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### Option A: npm (recommended)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g pm-skill
|
|
23
|
+
pm-skill help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Option B: npx (no install)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx pm-skill help
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Option C: Clone as Claude Code skill
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/Leonamin/pm-skill.git ~/.claude/skills/pm-skill
|
|
36
|
+
cd ~/.claude/skills/pm-skill && npm install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Option D: Clone for Codex
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/Leonamin/pm-skill.git ~/pm-skill
|
|
43
|
+
cd ~/pm-skill && npm install
|
|
44
|
+
# Codex will read AGENTS.md for instructions
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Setup
|
|
48
|
+
|
|
49
|
+
### 1. Create `.env`
|
|
50
|
+
|
|
51
|
+
Place `.env` in any of these locations (checked in order):
|
|
52
|
+
1. Current working directory
|
|
53
|
+
2. `~/.pm-skill/`
|
|
54
|
+
3. Package root
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Copy the template
|
|
58
|
+
cp .env.example ~/.pm-skill/.env
|
|
59
|
+
# Edit with your API keys
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```env
|
|
63
|
+
LINEAR_API_KEY=lin_api_xxxxxxxx
|
|
64
|
+
LINEAR_DEFAULT_TEAM_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
65
|
+
NOTION_API_KEY=secret_xxxxxxxx
|
|
66
|
+
NOTION_ROOT_PAGE_ID=xxxxxxxx
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. Run setup
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pm-skill setup
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This shows your Linear teams, workflow states, and labels — and verifies that `config.yml` labels match your Linear workspace.
|
|
76
|
+
|
|
77
|
+
### 3. Customize `config.yml`
|
|
78
|
+
|
|
79
|
+
Copy `config.yml` to `~/.pm-skill/config.yml` and edit labels, templates, priorities, and severity mappings to match your project.
|
|
80
|
+
|
|
81
|
+
**Rule: every label and template must have a `description` field.**
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Start a feature
|
|
87
|
+
pm-skill start-feature "Booking cancellation"
|
|
88
|
+
|
|
89
|
+
# Report a bug
|
|
90
|
+
pm-skill report-bug "Payment amount error" --severity high
|
|
91
|
+
|
|
92
|
+
# Add sub-tasks
|
|
93
|
+
pm-skill add-task ENG-10 "Write unit tests"
|
|
94
|
+
pm-skill add-task ENG-10 "Frontend UI"
|
|
95
|
+
|
|
96
|
+
# Link issues
|
|
97
|
+
pm-skill relate ENG-10 ENG-8 --type related
|
|
98
|
+
pm-skill block ENG-10 ENG-15
|
|
99
|
+
|
|
100
|
+
# Attach documents
|
|
101
|
+
pm-skill attach-doc ENG-10 --url "https://notion.so/..." --title "Design Doc" --type source-of-truth
|
|
102
|
+
|
|
103
|
+
# View issue details
|
|
104
|
+
pm-skill get ENG-10
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Using with AI Assistants
|
|
108
|
+
|
|
109
|
+
### Claude Code
|
|
110
|
+
|
|
111
|
+
If installed as a skill in `~/.claude/skills/pm-skill/`, Claude Code auto-discovers it via `SKILL.md`. You can invoke commands through natural language:
|
|
112
|
+
|
|
113
|
+
> "Create a feature issue for booking cancellation"
|
|
114
|
+
|
|
115
|
+
### Codex
|
|
116
|
+
|
|
117
|
+
Clone the repo and Codex reads `AGENTS.md` for command instructions. Run commands via:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npx tsx src/workflows.ts start-feature "Booking cancellation"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Any AI Assistant
|
|
124
|
+
|
|
125
|
+
Any assistant that can execute shell commands can use pm-skill:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pm-skill start-feature "My Feature"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Config Structure
|
|
132
|
+
|
|
133
|
+
| Section | Description |
|
|
134
|
+
|---------|-------------|
|
|
135
|
+
| `labels` | Available labels (description required) |
|
|
136
|
+
| `templates` | Command-to-label/priority/Notion-template mappings |
|
|
137
|
+
| `priorities` | Priority key (p0-p3) to Linear priority number mapping |
|
|
138
|
+
| `severity_mapping` | Severity name to priority key mapping |
|
|
139
|
+
| `doc_types` | Document types for attach-doc validation |
|
|
140
|
+
| `epics` | Epic definitions (project-specific) |
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# PM Skill — 정형화된 프로젝트 관리 도구
|
|
2
|
+
|
|
3
|
+
Linear와 Notion을 정형화된 틀 안에서 조작하는 프로젝트 관리 스킬.
|
|
4
|
+
"설계의 자유, 사용의 부자유" — config.yml에 정의된 라벨/템플릿/severity만 사용 가능.
|
|
5
|
+
|
|
6
|
+
## 설정
|
|
7
|
+
|
|
8
|
+
### 1. API 키 발급
|
|
9
|
+
|
|
10
|
+
**Linear**: Settings > API > Personal API Keys
|
|
11
|
+
**Notion**: https://www.notion.so/my-integrations > New integration
|
|
12
|
+
|
|
13
|
+
### 2. .env 생성
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cp .env.example .env
|
|
17
|
+
# LINEAR_API_KEY, NOTION_API_KEY 입력
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 3. Setup 실행
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx tsx src/workflows.ts setup
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
팀/상태/라벨 ID를 확인하고 `.env`에 `LINEAR_DEFAULT_TEAM_ID` 등을 설정.
|
|
27
|
+
|
|
28
|
+
### 4. Config 커스터마이징
|
|
29
|
+
|
|
30
|
+
`config.yml`에서 라벨, 템플릿, severity 매핑을 프로젝트에 맞게 수정.
|
|
31
|
+
**규칙: 모든 라벨/템플릿에 `description` 필수.** 없으면 로딩 시 에러.
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
labels:
|
|
35
|
+
- id: my-label
|
|
36
|
+
name: My Label
|
|
37
|
+
description: "이 라벨의 용도 설명" # ← 필수!
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 커맨드 레퍼런스
|
|
41
|
+
|
|
42
|
+
### setup
|
|
43
|
+
Linear/Notion 연결 상태 확인 + .env 안내.
|
|
44
|
+
```bash
|
|
45
|
+
npx tsx src/workflows.ts setup
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### start-feature
|
|
49
|
+
기능 개발 시작. Linear 이슈 + Notion PRD + 상호 URL 연결.
|
|
50
|
+
```bash
|
|
51
|
+
npx tsx src/workflows.ts start-feature "예약 취소 기능"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### report-bug
|
|
55
|
+
버그 리포트. severity로 우선순위 자동 매핑.
|
|
56
|
+
```bash
|
|
57
|
+
npx tsx src/workflows.ts report-bug "결제 금액 오류" --severity high
|
|
58
|
+
# severity: urgent, high, medium(기본), low
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### add-task
|
|
62
|
+
이슈에 하위 태스크 추가.
|
|
63
|
+
```bash
|
|
64
|
+
npx tsx src/workflows.ts add-task ENG-10 "단위 테스트 작성"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### relate
|
|
68
|
+
이슈 간 관계 설정. (related, similar)
|
|
69
|
+
```bash
|
|
70
|
+
npx tsx src/workflows.ts relate ENG-10 ENG-11 --type related
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### block
|
|
74
|
+
선행 관계 설정. (ENG-10 완료 후 ENG-11 진행)
|
|
75
|
+
```bash
|
|
76
|
+
npx tsx src/workflows.ts block ENG-10 ENG-11
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### attach-doc
|
|
80
|
+
이슈에 문서 URL 첨부. doc_type은 config 검증.
|
|
81
|
+
```bash
|
|
82
|
+
npx tsx src/workflows.ts attach-doc ENG-10 \
|
|
83
|
+
--url "https://notion.so/..." \
|
|
84
|
+
--title "결제 설계서" \
|
|
85
|
+
--type source-of-truth
|
|
86
|
+
# type: source-of-truth, issue-tracking, domain-knowledge
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### get
|
|
90
|
+
이슈 상세 조회. 하위이슈, 관계, 첨부문서 포함.
|
|
91
|
+
```bash
|
|
92
|
+
npx tsx src/workflows.ts get ENG-10
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 워크플로우 예시
|
|
96
|
+
|
|
97
|
+
### 기능 개발
|
|
98
|
+
```bash
|
|
99
|
+
# 1. 기능 시작
|
|
100
|
+
npx tsx src/workflows.ts start-feature "예약 취소 기능"
|
|
101
|
+
# → Linear ENG-10 + Notion PRD 생성
|
|
102
|
+
|
|
103
|
+
# 2. 하위 태스크 추가
|
|
104
|
+
npx tsx src/workflows.ts add-task ENG-10 "API 엔드포인트 구현"
|
|
105
|
+
npx tsx src/workflows.ts add-task ENG-10 "프론트엔드 UI"
|
|
106
|
+
npx tsx src/workflows.ts add-task ENG-10 "테스트 작성"
|
|
107
|
+
|
|
108
|
+
# 3. 관련 이슈 연결
|
|
109
|
+
npx tsx src/workflows.ts relate ENG-10 ENG-8 --type related
|
|
110
|
+
|
|
111
|
+
# 4. 선행 관계 설정
|
|
112
|
+
npx tsx src/workflows.ts block ENG-10 ENG-15
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 버그 수정
|
|
116
|
+
```bash
|
|
117
|
+
# 1. 버그 리포트
|
|
118
|
+
npx tsx src/workflows.ts report-bug "결제 금액 오류" --severity high
|
|
119
|
+
|
|
120
|
+
# 2. 하위 태스크
|
|
121
|
+
npx tsx src/workflows.ts add-task ENG-20 "원인 분석"
|
|
122
|
+
npx tsx src/workflows.ts add-task ENG-20 "수정 및 테스트"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Config 구조
|
|
126
|
+
|
|
127
|
+
| 섹션 | 설명 |
|
|
128
|
+
|------|------|
|
|
129
|
+
| `labels` | 사용 가능한 라벨 목록 (description 필수) |
|
|
130
|
+
| `templates` | 커맨드별 기본 라벨/우선순위/Notion 템플릿 매핑 |
|
|
131
|
+
| `priorities` | Plank 우선순위 → Linear 우선순위 매핑 (p0-p3) |
|
|
132
|
+
| `severity_mapping` | severity 이름 → priority 키 매핑 |
|
|
133
|
+
| `doc_types` | 문서 유형 (attach-doc의 --type 값) |
|
|
134
|
+
| `epics` | 에픽 목록 (프로젝트별 정의) |
|
package/config.yml
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
labels:
|
|
2
|
+
- id: dev
|
|
3
|
+
name: Development
|
|
4
|
+
description: "코드 개발 작업 (새 기능, 기존 기능 변경)"
|
|
5
|
+
color: "#0088ff"
|
|
6
|
+
- id: design
|
|
7
|
+
name: Design
|
|
8
|
+
description: "UI/UX 디자인 관련 작업"
|
|
9
|
+
color: "#ff00ff"
|
|
10
|
+
- id: plan
|
|
11
|
+
name: Planning
|
|
12
|
+
description: "기획, 설계, 의사결정 문서 작업"
|
|
13
|
+
color: "#00cc88"
|
|
14
|
+
- id: bug
|
|
15
|
+
name: Bug
|
|
16
|
+
description: "버그 수정"
|
|
17
|
+
color: "#ff0000"
|
|
18
|
+
- id: refactor
|
|
19
|
+
name: Refactor
|
|
20
|
+
description: "기존 코드 리팩토링 (기능 변경 없음)"
|
|
21
|
+
color: "#888888"
|
|
22
|
+
- id: admin
|
|
23
|
+
name: Admin
|
|
24
|
+
description: "인프라, 배포, 환경 설정 등 관리 작업"
|
|
25
|
+
color: "#ffaa00"
|
|
26
|
+
- id: guest
|
|
27
|
+
name: Guest
|
|
28
|
+
description: "외부 의존성, 서드파티 연동 관련"
|
|
29
|
+
color: "#aa88ff"
|
|
30
|
+
|
|
31
|
+
templates:
|
|
32
|
+
- id: feature
|
|
33
|
+
name: Feature PRD
|
|
34
|
+
description: "기능 개발용 PRD 템플릿"
|
|
35
|
+
notion_template: feature-prd
|
|
36
|
+
linear_labels: [dev]
|
|
37
|
+
linear_priority: p2
|
|
38
|
+
- id: bugfix
|
|
39
|
+
name: Bug Report
|
|
40
|
+
description: "버그 리포트 템플릿"
|
|
41
|
+
notion_template: bug-report
|
|
42
|
+
linear_labels: [bug]
|
|
43
|
+
- id: improvement
|
|
44
|
+
name: Improvement
|
|
45
|
+
description: "기존 기능 개선 템플릿"
|
|
46
|
+
notion_template: improvement
|
|
47
|
+
linear_labels: [dev]
|
|
48
|
+
linear_priority: p2
|
|
49
|
+
- id: refactor
|
|
50
|
+
name: Refactor
|
|
51
|
+
description: "리팩토링 템플릿"
|
|
52
|
+
notion_template: refactor
|
|
53
|
+
linear_labels: [refactor]
|
|
54
|
+
linear_priority: p3
|
|
55
|
+
- id: design
|
|
56
|
+
name: Design Doc
|
|
57
|
+
description: "설계 문서 템플릿"
|
|
58
|
+
notion_template: design-doc
|
|
59
|
+
linear_labels: [design, plan]
|
|
60
|
+
linear_priority: p2
|
|
61
|
+
|
|
62
|
+
priorities:
|
|
63
|
+
p0: { linear: 1, name: "Urgent" }
|
|
64
|
+
p1: { linear: 2, name: "High" }
|
|
65
|
+
p2: { linear: 3, name: "Medium" }
|
|
66
|
+
p3: { linear: 4, name: "Low" }
|
|
67
|
+
|
|
68
|
+
severity_mapping:
|
|
69
|
+
urgent: p0
|
|
70
|
+
high: p1
|
|
71
|
+
medium: p2
|
|
72
|
+
low: p3
|
|
73
|
+
|
|
74
|
+
doc_types:
|
|
75
|
+
- id: source-of-truth
|
|
76
|
+
description: "진실의 원천 문서 — 프로젝트의 공식 기준이 되는 문서"
|
|
77
|
+
- id: issue-tracking
|
|
78
|
+
description: "장기 이슈 추적 문서 — 해결까지 시간이 걸리는 문제 기록"
|
|
79
|
+
- id: domain-knowledge
|
|
80
|
+
description: "도메인 지식 문서 — 프로젝트 도메인 관련 학습/참고 자료"
|
|
81
|
+
|
|
82
|
+
epics: []
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface PmLabel {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
color?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PmTemplate {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
notion_template: string;
|
|
12
|
+
linear_labels: string[];
|
|
13
|
+
linear_priority?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface PmPriority {
|
|
16
|
+
linear: number;
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PmDocType {
|
|
20
|
+
id: string;
|
|
21
|
+
description: string;
|
|
22
|
+
}
|
|
23
|
+
export interface PmEpic {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
}
|
|
27
|
+
export interface PmConfig {
|
|
28
|
+
labels: PmLabel[];
|
|
29
|
+
templates: PmTemplate[];
|
|
30
|
+
priorities: Record<string, PmPriority>;
|
|
31
|
+
severity_mapping: Record<string, string>;
|
|
32
|
+
doc_types: PmDocType[];
|
|
33
|
+
epics: PmEpic[];
|
|
34
|
+
}
|
|
35
|
+
export declare function loadConfig(configPath?: string): PmConfig;
|
|
36
|
+
export declare function validateLabel(config: PmConfig, labelId: string): PmLabel;
|
|
37
|
+
export declare function validateDocType(config: PmConfig, typeId: string): PmDocType;
|
|
38
|
+
export declare function getTemplate(config: PmConfig, templateId: string): PmTemplate;
|
|
39
|
+
export declare function resolvePriority(config: PmConfig, priorityKey: string): number;
|
|
40
|
+
export declare function resolveSeverity(config: PmConfig, severity: string): number;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
import { resolveFile } from "./env.js";
|
|
4
|
+
// ── Validation ──
|
|
5
|
+
class ConfigValidationError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(`[Config validation failed] ${message}`);
|
|
8
|
+
this.name = "ConfigValidationError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function validateLabels(labels) {
|
|
12
|
+
if (!Array.isArray(labels) || labels.length === 0) {
|
|
13
|
+
throw new ConfigValidationError("labels array is empty or missing.");
|
|
14
|
+
}
|
|
15
|
+
for (const label of labels) {
|
|
16
|
+
if (!label.id || !label.name) {
|
|
17
|
+
throw new ConfigValidationError(`Label missing id or name: ${JSON.stringify(label)}`);
|
|
18
|
+
}
|
|
19
|
+
if (!label.description) {
|
|
20
|
+
throw new ConfigValidationError(`Label '${label.id}' is missing description. All labels require a description.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function validateTemplates(templates, labelIds) {
|
|
25
|
+
if (!Array.isArray(templates) || templates.length === 0) {
|
|
26
|
+
throw new ConfigValidationError("templates array is empty or missing.");
|
|
27
|
+
}
|
|
28
|
+
for (const tmpl of templates) {
|
|
29
|
+
if (!tmpl.id || !tmpl.name || !tmpl.description || !tmpl.notion_template) {
|
|
30
|
+
throw new ConfigValidationError(`Template '${tmpl.id ?? "(unknown)"}' is missing required fields (id, name, description, notion_template).`);
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(tmpl.linear_labels)) {
|
|
33
|
+
for (const lid of tmpl.linear_labels) {
|
|
34
|
+
if (!labelIds.has(lid)) {
|
|
35
|
+
throw new ConfigValidationError(`Template '${tmpl.id}' references unregistered label '${lid}' in linear_labels.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function validatePriorities(priorities) {
|
|
42
|
+
if (!priorities || typeof priorities !== "object") {
|
|
43
|
+
throw new ConfigValidationError("priorities is missing.");
|
|
44
|
+
}
|
|
45
|
+
for (const key of ["p0", "p1", "p2", "p3"]) {
|
|
46
|
+
const p = priorities[key];
|
|
47
|
+
if (!p ||
|
|
48
|
+
typeof p.linear !== "number" ||
|
|
49
|
+
typeof p.name !== "string") {
|
|
50
|
+
throw new ConfigValidationError(`priorities.${key} requires linear (number) and name (string).`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function validateSeverityMapping(mapping, priorityKeys) {
|
|
55
|
+
if (!mapping || typeof mapping !== "object") {
|
|
56
|
+
throw new ConfigValidationError("severity_mapping is missing.");
|
|
57
|
+
}
|
|
58
|
+
for (const [severity, pKey] of Object.entries(mapping)) {
|
|
59
|
+
if (!priorityKeys.has(pKey)) {
|
|
60
|
+
throw new ConfigValidationError(`severity_mapping '${severity}: ${pKey}' — '${pKey}' is not defined in priorities.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function validateDocTypes(docTypes) {
|
|
65
|
+
if (!Array.isArray(docTypes) || docTypes.length === 0) {
|
|
66
|
+
throw new ConfigValidationError("doc_types array is empty or missing.");
|
|
67
|
+
}
|
|
68
|
+
for (const dt of docTypes) {
|
|
69
|
+
if (!dt.id || !dt.description) {
|
|
70
|
+
throw new ConfigValidationError(`doc_type '${dt.id ?? "(unknown)"}' is missing id or description.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ── Loader ──
|
|
75
|
+
export function loadConfig(configPath) {
|
|
76
|
+
const path = configPath ?? resolveFile("config.yml");
|
|
77
|
+
if (!path) {
|
|
78
|
+
throw new ConfigValidationError("config.yml not found. Looked in: CWD, ~/.pm-skill/, package root.\n" +
|
|
79
|
+
"Copy the bundled config.yml to one of these locations and customize it.");
|
|
80
|
+
}
|
|
81
|
+
let raw;
|
|
82
|
+
try {
|
|
83
|
+
const content = readFileSync(path, "utf-8");
|
|
84
|
+
raw = yaml.load(content);
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
throw new ConfigValidationError(`Cannot read config.yml: ${path}\n${e.message}`);
|
|
88
|
+
}
|
|
89
|
+
validateLabels(raw.labels);
|
|
90
|
+
const labelIds = new Set(raw.labels.map((l) => l.id));
|
|
91
|
+
validateTemplates(raw.templates, labelIds);
|
|
92
|
+
validatePriorities(raw.priorities);
|
|
93
|
+
const priorityKeys = new Set(Object.keys(raw.priorities));
|
|
94
|
+
validateSeverityMapping(raw.severity_mapping, priorityKeys);
|
|
95
|
+
validateDocTypes(raw.doc_types);
|
|
96
|
+
return {
|
|
97
|
+
labels: raw.labels,
|
|
98
|
+
templates: raw.templates,
|
|
99
|
+
priorities: raw.priorities,
|
|
100
|
+
severity_mapping: raw.severity_mapping,
|
|
101
|
+
doc_types: raw.doc_types,
|
|
102
|
+
epics: Array.isArray(raw.epics) ? raw.epics : [],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// ── Query helpers ──
|
|
106
|
+
export function validateLabel(config, labelId) {
|
|
107
|
+
const label = config.labels.find((l) => l.id === labelId);
|
|
108
|
+
if (!label) {
|
|
109
|
+
const available = config.labels.map((l) => l.id).join(", ");
|
|
110
|
+
throw new Error(`Label '${labelId}' is not defined in config.\nAvailable: ${available}`);
|
|
111
|
+
}
|
|
112
|
+
return label;
|
|
113
|
+
}
|
|
114
|
+
export function validateDocType(config, typeId) {
|
|
115
|
+
const dt = config.doc_types.find((d) => d.id === typeId);
|
|
116
|
+
if (!dt) {
|
|
117
|
+
const available = config.doc_types.map((d) => d.id).join(", ");
|
|
118
|
+
throw new Error(`Doc type '${typeId}' is not defined in config.\nAvailable: ${available}`);
|
|
119
|
+
}
|
|
120
|
+
return dt;
|
|
121
|
+
}
|
|
122
|
+
export function getTemplate(config, templateId) {
|
|
123
|
+
const tmpl = config.templates.find((t) => t.id === templateId);
|
|
124
|
+
if (!tmpl) {
|
|
125
|
+
const available = config.templates.map((t) => t.id).join(", ");
|
|
126
|
+
throw new Error(`Template '${templateId}' is not defined in config.\nAvailable: ${available}`);
|
|
127
|
+
}
|
|
128
|
+
return tmpl;
|
|
129
|
+
}
|
|
130
|
+
export function resolvePriority(config, priorityKey) {
|
|
131
|
+
const p = config.priorities[priorityKey];
|
|
132
|
+
if (!p) {
|
|
133
|
+
throw new Error(`Priority '${priorityKey}' is not defined in config.`);
|
|
134
|
+
}
|
|
135
|
+
return p.linear;
|
|
136
|
+
}
|
|
137
|
+
export function resolveSeverity(config, severity) {
|
|
138
|
+
const pKey = config.severity_mapping[severity];
|
|
139
|
+
if (!pKey) {
|
|
140
|
+
const available = Object.keys(config.severity_mapping).join(", ");
|
|
141
|
+
throw new Error(`Severity '${severity}' is not defined in config severity_mapping.\nAvailable: ${available}`);
|
|
142
|
+
}
|
|
143
|
+
return resolvePriority(config, pKey);
|
|
144
|
+
}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const GLOBAL_DIR: string;
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a file by checking multiple directories in order:
|
|
4
|
+
* 1. CWD (project-local override)
|
|
5
|
+
* 2. ~/.pm-skill/ (user-global config)
|
|
6
|
+
* 3. Package root (bundled defaults)
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveFile(filename: string): string | null;
|
|
9
|
+
export interface ValidatedEnv {
|
|
10
|
+
LINEAR_API_KEY: string;
|
|
11
|
+
LINEAR_DEFAULT_TEAM_ID: string;
|
|
12
|
+
LINEAR_DEFAULT_PROJECT_ID?: string;
|
|
13
|
+
NOTION_API_KEY?: string;
|
|
14
|
+
NOTION_ROOT_PAGE_ID?: string;
|
|
15
|
+
NOTION_BUG_DB_ID?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Hierarchical .env loading:
|
|
19
|
+
* 1. CWD/.env (project-specific, highest priority)
|
|
20
|
+
* 2. ~/.pm-skill/.env (global defaults, fills in missing keys)
|
|
21
|
+
*
|
|
22
|
+
* Both files are loaded. CWD values take precedence over global.
|
|
23
|
+
*/
|
|
24
|
+
export declare function loadEnvFiles(): void;
|
|
25
|
+
export declare function validateEnv(command: string): ValidatedEnv;
|
|
26
|
+
/**
|
|
27
|
+
* Write key=value pairs to a .env file.
|
|
28
|
+
* If the file exists, updates existing keys and appends new ones.
|
|
29
|
+
* If not, creates it fresh.
|
|
30
|
+
*/
|
|
31
|
+
export declare function writeEnvFile(targetDir: string, entries: Record<string, string>): string;
|