pm-skill 1.0.2 → 1.1.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/AGENTS.md CHANGED
@@ -9,11 +9,7 @@ A structured project management CLI that integrates Linear (issue tracking) and
9
9
  ## How to Run Commands
10
10
 
11
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]
12
+ npx pm-skill <command> [args] [flags]
17
13
  ```
18
14
 
19
15
  ## Available Commands
@@ -21,67 +17,69 @@ npx tsx src/workflows.ts <command> [args] [flags]
21
17
  ### setup
22
18
  Verify Linear/Notion connection and show team/label configuration.
23
19
  ```bash
24
- pm-skill setup
20
+ npx pm-skill setup
21
+ npx pm-skill setup --sync # create missing labels in Linear
25
22
  ```
26
23
 
27
24
  ### start-feature
28
25
  Create a Linear issue + Notion PRD page with bidirectional links.
29
26
  ```bash
30
- pm-skill start-feature "<title>"
27
+ npx pm-skill start-feature "<title>"
31
28
  ```
32
29
 
33
30
  ### report-bug
34
31
  File a bug report with severity-based priority mapping.
35
32
  ```bash
36
- pm-skill report-bug "<title>" --severity <urgent|high|medium|low>
33
+ npx pm-skill report-bug "<title>" --severity <urgent|high|medium|low>
37
34
  ```
38
35
  Default severity: medium.
39
36
 
40
37
  ### add-task
41
38
  Add a sub-issue to a parent issue.
42
39
  ```bash
43
- pm-skill add-task <parent-issue-id> "<title>"
40
+ npx pm-skill add-task <parent-issue-id> "<title>"
44
41
  ```
45
42
 
46
43
  ### relate
47
44
  Set a relationship between two issues.
48
45
  ```bash
49
- pm-skill relate <issue1> <issue2> --type <related|similar>
46
+ npx pm-skill relate <issue1> <issue2> --type <related|similar>
50
47
  ```
51
48
  Default type: related.
52
49
 
53
50
  ### block
54
51
  Set a blocking dependency (issue1 blocks issue2).
55
52
  ```bash
56
- pm-skill block <blocker-issue> <blocked-issue>
53
+ npx pm-skill block <blocker-issue> <blocked-issue>
57
54
  ```
58
55
 
59
56
  ### attach-doc
60
57
  Attach a document URL to an issue with type validation.
61
58
  ```bash
62
- pm-skill attach-doc <issue> --url "<url>" --title "<title>" --type <source-of-truth|issue-tracking|domain-knowledge>
59
+ npx pm-skill attach-doc <issue> --url "<url>" --title "<title>" --type <source-of-truth|issue-tracking|domain-knowledge>
63
60
  ```
64
61
 
65
62
  ### get
66
63
  Show issue details including children, relations, and attachments.
67
64
  ```bash
68
- pm-skill get <issue-id>
65
+ npx pm-skill get <issue-id>
69
66
  ```
70
67
 
71
68
  ## Configuration
72
69
 
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.
70
+ All config is per-project (CWD):
71
+ - **`.env`** — API keys and IDs
72
+ - **`config.yml`** — Labels, templates, priorities, severity mappings
75
73
 
76
74
  ## Workflow Patterns
77
75
 
78
76
  ### 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`
77
+ 1. `npx pm-skill start-feature "Feature Name"` — creates Linear issue + Notion PRD
78
+ 2. `npx pm-skill add-task ENG-XX "Sub-task 1"` — break down into sub-tasks
79
+ 3. `npx pm-skill relate ENG-XX ENG-YY` — link related issues
80
+ 4. `npx pm-skill attach-doc ENG-XX --url "..." --title "..." --type source-of-truth`
83
81
 
84
82
  ### 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"`
83
+ 1. `npx pm-skill report-bug "Bug Description" --severity high`
84
+ 2. `npx pm-skill add-task ENG-XX "Root cause analysis"`
85
+ 3. `npx pm-skill add-task ENG-XX "Fix and test"`
package/README.md CHANGED
@@ -6,77 +6,61 @@ Structured project management CLI that integrates **Linear** and **Notion**. Des
6
6
 
7
7
  ## Features
8
8
 
9
+ - **init** — Validate API keys, create `.env`, `config.yml`, `SKILL.md`, `AGENTS.md` in your project
10
+ - **setup** — Verify connections, label matching, Notion status (`--sync` creates missing labels)
9
11
  - **start-feature** — Create Linear issue + Notion PRD page, auto-linked
10
12
  - **report-bug** — File bug with severity-based priority mapping
11
13
  - **add-task** — Add sub-issues to a parent
12
14
  - **relate / block** — Set issue relationships and dependencies
13
15
  - **attach-doc** — Attach documents with type validation
14
16
  - **get** — View issue details with children, relations, and attachments
15
- - **setup** — Verify connections and discover team/label IDs
16
17
 
17
- ## Installation
18
-
19
- ### Option A: npm (recommended)
18
+ ## Quick Start
20
19
 
21
20
  ```bash
22
- npm install -g pm-skill
23
- pm-skill help
24
- ```
25
-
26
- ### Option B: npx (no install)
21
+ cd your-project
27
22
 
28
- ```bash
29
- npx pm-skill help
30
- ```
23
+ # Initialize (validates keys, creates config files)
24
+ npx pm-skill init --linear-key lin_api_xxx --notion-key secret_xxx
31
25
 
32
- ### Option C: Clone as Claude Code skill
26
+ # Verify label matching
27
+ npx pm-skill setup
33
28
 
34
- ```bash
35
- git clone https://github.com/Leonamin/pm-skill.git ~/.claude/skills/pm-skill
36
- cd ~/.claude/skills/pm-skill && npm install
29
+ # Create missing labels in Linear
30
+ npx pm-skill setup --sync
37
31
  ```
38
32
 
39
- ### Option D: Clone for Codex
33
+ This creates the following in your project:
40
34
 
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
35
+ ```
36
+ your-project/
37
+ ├── .env # API keys + project settings
38
+ ├── config.yml # Labels, templates, priorities
39
+ ├── .claude/skills/pm-skill/SKILL.md # Claude Code auto-discovers this
40
+ ├── AGENTS.md # Codex auto-discovers this
41
+ └── ...
45
42
  ```
46
43
 
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
44
+ ## Setup Details
55
45
 
56
- ```bash
57
- # Copy the template
58
- cp .env.example ~/.pm-skill/.env
59
- # Edit with your API keys
60
- ```
46
+ ### API Keys
61
47
 
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
- ```
48
+ - **Linear**: Settings > API > Personal API Keys
49
+ - **Notion**: https://www.notion.so/my-integrations > New integration
68
50
 
69
- ### 2. Run setup
51
+ ### init options
70
52
 
71
53
  ```bash
72
- pm-skill setup
54
+ npx pm-skill init --linear-key <key> [options]
55
+ --notion-key Notion API key (optional)
56
+ --team-id Linear team ID (auto-detected if omitted)
57
+ --project-id Linear project ID (optional)
58
+ --notion-page Notion root page ID (optional)
73
59
  ```
74
60
 
75
- This shows your Linear teams, workflow states, and labels — and verifies that `config.yml` labels match your Linear workspace.
61
+ ### Customize `config.yml`
76
62
 
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.
63
+ Edit `config.yml` in your project root to match your labels, templates, priorities, and severity mappings.
80
64
 
81
65
  **Rule: every label and template must have a `description` field.**
82
66
 
@@ -84,48 +68,47 @@ Copy `config.yml` to `~/.pm-skill/config.yml` and edit labels, templates, priori
84
68
 
85
69
  ```bash
86
70
  # Start a feature
87
- pm-skill start-feature "Booking cancellation"
71
+ npx pm-skill start-feature "Booking cancellation"
88
72
 
89
73
  # Report a bug
90
- pm-skill report-bug "Payment amount error" --severity high
74
+ npx pm-skill report-bug "Payment amount error" --severity high
91
75
 
92
76
  # Add sub-tasks
93
- pm-skill add-task ENG-10 "Write unit tests"
94
- pm-skill add-task ENG-10 "Frontend UI"
77
+ npx pm-skill add-task ENG-10 "Write unit tests"
78
+ npx pm-skill add-task ENG-10 "Frontend UI"
95
79
 
96
80
  # Link issues
97
- pm-skill relate ENG-10 ENG-8 --type related
98
- pm-skill block ENG-10 ENG-15
81
+ npx pm-skill relate ENG-10 ENG-8 --type related
82
+ npx pm-skill block ENG-10 ENG-15
99
83
 
100
84
  # Attach documents
101
- pm-skill attach-doc ENG-10 --url "https://notion.so/..." --title "Design Doc" --type source-of-truth
85
+ npx pm-skill attach-doc ENG-10 --url "https://notion.so/..." --title "Design Doc" --type source-of-truth
102
86
 
103
87
  # View issue details
104
- pm-skill get ENG-10
88
+ npx pm-skill get ENG-10
89
+
90
+ # Check version
91
+ npx pm-skill --version
105
92
  ```
106
93
 
107
94
  ## Using with AI Assistants
108
95
 
109
96
  ### Claude Code
110
97
 
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:
98
+ After `npx pm-skill init`, Claude Code auto-discovers the skill via `.claude/skills/pm-skill/SKILL.md`. You can invoke commands through natural language:
112
99
 
113
100
  > "Create a feature issue for booking cancellation"
114
101
 
115
102
  ### Codex
116
103
 
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
- ```
104
+ After `npx pm-skill init`, Codex reads `AGENTS.md` at the project root for command instructions.
122
105
 
123
106
  ### Any AI Assistant
124
107
 
125
108
  Any assistant that can execute shell commands can use pm-skill:
126
109
 
127
110
  ```bash
128
- pm-skill start-feature "My Feature"
111
+ npx pm-skill start-feature "My Feature"
129
112
  ```
130
113
 
131
114
  ## Config Structure
@@ -139,6 +122,10 @@ pm-skill start-feature "My Feature"
139
122
  | `doc_types` | Document types for attach-doc validation |
140
123
  | `epics` | Epic definitions (project-specific) |
141
124
 
125
+ ## Per-Project Model
126
+
127
+ All config is per-project. Each project gets its own `.env`, `config.yml`, and instruction files. Run `npx pm-skill init` in each project directory.
128
+
142
129
  ## License
143
130
 
144
131
  MIT
package/SKILL.md CHANGED
@@ -1,134 +1,102 @@
1
- # PM Skill — 정형화된 프로젝트 관리 도구
1
+ # PM Skill — Structured Project Management
2
2
 
3
- Linear Notion 정형화된 안에서 조작하는 프로젝트 관리 스킬.
4
- "설계의 자유, 사용의 부자유" — config.yml 정의된 라벨/템플릿/severity만 사용 가능.
3
+ Linear + Notion integration for structured project management.
4
+ "Design freedom, usage discipline" — only labels/templates/severity defined in `config.yml` are allowed.
5
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 실행
6
+ ## Setup
21
7
 
22
8
  ```bash
23
- npx tsx src/workflows.ts setup
24
- ```
25
-
26
- 팀/상태/라벨 ID를 확인하고 `.env`에 `LINEAR_DEFAULT_TEAM_ID` 등을 설정.
9
+ # Initialize in project directory (validates keys, creates .env + config.yml)
10
+ npx pm-skill init --linear-key <key> --notion-key <key>
27
11
 
28
- ### 4. Config 커스터마이징
12
+ # Verify label matching
13
+ npx pm-skill setup
29
14
 
30
- `config.yml`에서 라벨, 템플릿, severity 매핑을 프로젝트에 맞게 수정.
31
- **규칙: 모든 라벨/템플릿에 `description` 필수.** 없으면 로딩 시 에러.
32
-
33
- ```yaml
34
- labels:
35
- - id: my-label
36
- name: My Label
37
- description: "이 라벨의 용도 설명" # ← 필수!
15
+ # Create missing labels in Linear
16
+ npx pm-skill setup --sync
38
17
  ```
39
18
 
40
- ## 커맨드 레퍼런스
19
+ ## Commands
41
20
 
42
- ### setup
43
- Linear/Notion 연결 상태 확인 + .env 안내.
21
+ ### setup [--sync]
22
+ Verify Linear/Notion connection + label matching. `--sync` creates missing labels.
44
23
  ```bash
45
- npx tsx src/workflows.ts setup
24
+ npx pm-skill setup
46
25
  ```
47
26
 
48
27
  ### start-feature
49
- 기능 개발 시작. Linear 이슈 + Notion PRD + 상호 URL 연결.
28
+ Start feature development. Creates Linear issue + Notion PRD + bidirectional links.
50
29
  ```bash
51
- npx tsx src/workflows.ts start-feature "예약 취소 기능"
30
+ npx pm-skill start-feature "Feature title"
52
31
  ```
53
32
 
54
33
  ### report-bug
55
- 버그 리포트. severity로 우선순위 자동 매핑.
34
+ File bug report. Severity maps to Linear priority.
56
35
  ```bash
57
- npx tsx src/workflows.ts report-bug "결제 금액 오류" --severity high
58
- # severity: urgent, high, medium(기본), low
36
+ npx pm-skill report-bug "Bug title" --severity high
37
+ # severity: urgent, high, medium (default), low
59
38
  ```
60
39
 
61
40
  ### add-task
62
- 이슈에 하위 태스크 추가.
41
+ Add sub-task to an issue.
63
42
  ```bash
64
- npx tsx src/workflows.ts add-task ENG-10 "단위 테스트 작성"
43
+ npx pm-skill add-task ENG-10 "Write unit tests"
65
44
  ```
66
45
 
67
46
  ### relate
68
- 이슈 관계 설정. (related, similar)
47
+ Link two issues. (related, similar)
69
48
  ```bash
70
- npx tsx src/workflows.ts relate ENG-10 ENG-11 --type related
49
+ npx pm-skill relate ENG-10 ENG-11 --type related
71
50
  ```
72
51
 
73
52
  ### block
74
- 선행 관계 설정. (ENG-10 완료 ENG-11 진행)
53
+ Set blocking relationship. (ENG-10 must complete before ENG-11)
75
54
  ```bash
76
- npx tsx src/workflows.ts block ENG-10 ENG-11
55
+ npx pm-skill block ENG-10 ENG-11
77
56
  ```
78
57
 
79
58
  ### attach-doc
80
- 이슈에 문서 URL 첨부. doc_type은 config 검증.
59
+ Attach document URL to issue. Type is validated against config.
81
60
  ```bash
82
- npx tsx src/workflows.ts attach-doc ENG-10 \
61
+ npx pm-skill attach-doc ENG-10 \
83
62
  --url "https://notion.so/..." \
84
- --title "결제 설계서" \
63
+ --title "Design Doc" \
85
64
  --type source-of-truth
86
65
  # type: source-of-truth, issue-tracking, domain-knowledge
87
66
  ```
88
67
 
89
68
  ### get
90
- 이슈 상세 조회. 하위이슈, 관계, 첨부문서 포함.
69
+ Show issue details including sub-issues, relations, and attachments.
91
70
  ```bash
92
- npx tsx src/workflows.ts get ENG-10
71
+ npx pm-skill get ENG-10
93
72
  ```
94
73
 
95
- ## 워크플로우 예시
74
+ ## Workflow Examples
96
75
 
97
- ### 기능 개발
76
+ ### Feature Development
98
77
  ```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
78
+ npx pm-skill start-feature "Booking cancellation"
79
+ npx pm-skill add-task ENG-10 "API endpoint"
80
+ npx pm-skill add-task ENG-10 "Frontend UI"
81
+ npx pm-skill add-task ENG-10 "Tests"
82
+ npx pm-skill relate ENG-10 ENG-8 --type related
83
+ npx pm-skill block ENG-10 ENG-15
113
84
  ```
114
85
 
115
- ### 버그 수정
86
+ ### Bug Fix
116
87
  ```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 "수정 및 테스트"
88
+ npx pm-skill report-bug "Payment error" --severity high
89
+ npx pm-skill add-task ENG-20 "Root cause analysis"
90
+ npx pm-skill add-task ENG-20 "Fix and test"
123
91
  ```
124
92
 
125
- ## Config 구조
93
+ ## Config
126
94
 
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` | 에픽 목록 (프로젝트별 정의) |
95
+ | Section | Description |
96
+ |---------|-------------|
97
+ | `labels` | Available labels (description required) |
98
+ | `templates` | Command label/priority/Notion template mappings |
99
+ | `priorities` | p0-p3 → Linear priority mapping |
100
+ | `severity_mapping` | severity name → priority key |
101
+ | `doc_types` | Document types for attach-doc |
102
+ | `epics` | Epic definitions |
package/dist/env.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- export declare const GLOBAL_DIR: string;
1
+ export declare const PKG_ROOT: string;
2
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)
3
+ * Resolve a file by checking:
4
+ * 1. CWD (project config)
5
+ * 2. Package root (bundled defaults — for config.yml, SKILL.md, AGENTS.md)
7
6
  */
8
7
  export declare function resolveFile(filename: string): string | null;
9
8
  export interface ValidatedEnv {
@@ -15,17 +14,13 @@ export interface ValidatedEnv {
15
14
  NOTION_BUG_DB_ID?: string;
16
15
  }
17
16
  /**
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.
17
+ * Parse CWD/.env and set values into process.env.
18
+ * Does NOT overwrite existing env vars.
23
19
  */
24
- export declare function loadEnvFiles(): void;
20
+ export declare function loadEnvFile(): void;
25
21
  export declare function validateEnv(command: string): ValidatedEnv;
26
22
  /**
27
- * Write key=value pairs to a .env file.
23
+ * Write key=value pairs to a .env file in the given directory.
28
24
  * If the file exists, updates existing keys and appends new ones.
29
- * If not, creates it fresh.
30
25
  */
31
26
  export declare function writeEnvFile(targetDir: string, entries: Record<string, string>): string;
package/dist/env.js CHANGED
@@ -1,20 +1,16 @@
1
1
  import { resolve, dirname } from "path";
2
2
  import { fileURLToPath } from "url";
3
3
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
4
- import { homedir } from "os";
5
4
  const __dirname = dirname(fileURLToPath(import.meta.url));
6
- const PKG_ROOT = resolve(__dirname, "..");
7
- export const GLOBAL_DIR = resolve(homedir(), ".pm-skill");
5
+ export const PKG_ROOT = resolve(__dirname, "..");
8
6
  /**
9
- * Resolve a file by checking multiple directories in order:
10
- * 1. CWD (project-local override)
11
- * 2. ~/.pm-skill/ (user-global config)
12
- * 3. Package root (bundled defaults)
7
+ * Resolve a file by checking:
8
+ * 1. CWD (project config)
9
+ * 2. Package root (bundled defaults — for config.yml, SKILL.md, AGENTS.md)
13
10
  */
14
11
  export function resolveFile(filename) {
15
12
  const candidates = [
16
13
  resolve(process.cwd(), filename),
17
- resolve(GLOBAL_DIR, filename),
18
14
  resolve(PKG_ROOT, filename),
19
15
  ];
20
16
  for (const p of candidates) {
@@ -27,18 +23,21 @@ const REQUIRED_KEYS = ["LINEAR_API_KEY", "LINEAR_DEFAULT_TEAM_ID"];
27
23
  const NOTION_KEYS = ["NOTION_API_KEY", "NOTION_ROOT_PAGE_ID"];
28
24
  const KEY_HELP = {
29
25
  LINEAR_API_KEY: "Linear > Settings > API > Personal API Keys",
30
- LINEAR_DEFAULT_TEAM_ID: "'pm-skill init' or 'pm-skill setup' to discover your team ID",
26
+ LINEAR_DEFAULT_TEAM_ID: "'npx pm-skill init' to discover your team ID",
31
27
  LINEAR_DEFAULT_PROJECT_ID: "Linear > Project Settings (optional)",
32
28
  NOTION_API_KEY: "https://www.notion.so/my-integrations",
33
29
  NOTION_ROOT_PAGE_ID: "Parent page ID where Notion docs will be created",
34
30
  NOTION_BUG_DB_ID: "Notion DB ID for bug tracking (optional — falls back to page creation)",
35
31
  };
36
32
  /**
37
- * Parse a single .env file and set values into process.env.
38
- * Does NOT overwrite existing values (earlier loads take precedence).
33
+ * Parse CWD/.env and set values into process.env.
34
+ * Does NOT overwrite existing env vars.
39
35
  */
40
- function parseEnvFile(filePath) {
41
- const content = readFileSync(filePath, "utf-8");
36
+ export function loadEnvFile() {
37
+ const envPath = resolve(process.cwd(), ".env");
38
+ if (!existsSync(envPath))
39
+ return;
40
+ const content = readFileSync(envPath, "utf-8");
42
41
  for (const line of content.split("\n")) {
43
42
  const trimmed = line.trim();
44
43
  if (!trimmed || trimmed.startsWith("#"))
@@ -53,25 +52,8 @@ function parseEnvFile(filePath) {
53
52
  }
54
53
  }
55
54
  }
56
- /**
57
- * Hierarchical .env loading:
58
- * 1. CWD/.env (project-specific, highest priority)
59
- * 2. ~/.pm-skill/.env (global defaults, fills in missing keys)
60
- *
61
- * Both files are loaded. CWD values take precedence over global.
62
- */
63
- export function loadEnvFiles() {
64
- const cwdEnv = resolve(process.cwd(), ".env");
65
- const globalEnv = resolve(GLOBAL_DIR, ".env");
66
- // CWD first — these values win
67
- if (existsSync(cwdEnv))
68
- parseEnvFile(cwdEnv);
69
- // Global second — fills in anything CWD didn't set
70
- if (existsSync(globalEnv))
71
- parseEnvFile(globalEnv);
72
- }
73
55
  export function validateEnv(command) {
74
- loadEnvFiles();
56
+ loadEnvFile();
75
57
  const missing = [];
76
58
  if (command === "setup" || command === "init") {
77
59
  if (!process.env.LINEAR_API_KEY) {
@@ -96,8 +78,7 @@ export function validateEnv(command) {
96
78
  .map((k) => ` ${k}: ${KEY_HELP[k] ?? ""}`)
97
79
  .join("\n");
98
80
  throw new Error(`Required environment variables are not set:\n${hints}\n\n` +
99
- `Run 'pm-skill init' to set up, or create .env manually.\n` +
100
- `Config lookup: CWD/.env → ~/.pm-skill/.env (both loaded, CWD wins).`);
81
+ `Run 'npx pm-skill init' in your project directory to set up.`);
101
82
  }
102
83
  return {
103
84
  LINEAR_API_KEY: process.env.LINEAR_API_KEY,
@@ -109,9 +90,8 @@ export function validateEnv(command) {
109
90
  };
110
91
  }
111
92
  /**
112
- * Write key=value pairs to a .env file.
93
+ * Write key=value pairs to a .env file in the given directory.
113
94
  * If the file exists, updates existing keys and appends new ones.
114
- * If not, creates it fresh.
115
95
  */
116
96
  export function writeEnvFile(targetDir, entries) {
117
97
  mkdirSync(targetDir, { recursive: true });
@@ -133,7 +113,6 @@ export function writeEnvFile(targetDir, entries) {
133
113
  }
134
114
  const key = trimmed.slice(0, eqIdx).trim();
135
115
  existing.set(key, trimmed);
136
- // Will be replaced or kept
137
116
  if (key in entries) {
138
117
  lines.push(`${key}=${entries[key]}`);
139
118
  }
@@ -142,7 +121,6 @@ export function writeEnvFile(targetDir, entries) {
142
121
  }
143
122
  }
144
123
  }
145
- // Append new keys not already in file
146
124
  for (const [key, val] of Object.entries(entries)) {
147
125
  if (!existing.has(key)) {
148
126
  lines.push(`${key}=${val}`);
package/dist/workflows.js CHANGED
@@ -1,49 +1,42 @@
1
1
  #!/usr/bin/env node
2
2
  import minimist from "minimist";
3
- import { validateEnv, writeEnvFile, GLOBAL_DIR } from "./env.js";
3
+ import { existsSync, copyFileSync, mkdirSync, readFileSync } from "fs";
4
+ import { resolve, dirname } from "path";
5
+ import { validateEnv, writeEnvFile, PKG_ROOT } from "./env.js";
4
6
  import { loadConfig, getTemplate, resolvePriority, resolveSeverity, validateDocType, validateLabel, } from "./config.js";
5
7
  import { getLinearClient, validateLinearKey, createIssue, getIssue, getIssueDetail, createRelation, createAttachment, createLabel, getTeams, getTeamStates, getTeamLabels, resolveLabels, } from "./linear.js";
6
8
  import { getNotionClient, createTemplatedPage, createDatabaseEntry, validateNotionKey, } from "./notion.js";
7
- // ── Init (runs before context — no env/config validation needed) ──
8
- import { existsSync, copyFileSync, mkdirSync } from "fs";
9
- import { resolve, dirname } from "path";
10
- import { fileURLToPath } from "url";
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const PKG_ROOT = resolve(__dirname, "..");
13
- function copyDefaultConfig(targetDir) {
14
- const targetConfig = resolve(targetDir, "config.yml");
15
- if (existsSync(targetConfig)) {
16
- console.log(` config.yml already exists at ${targetConfig} — skipped`);
9
+ // ── Init ──
10
+ function copyBundledFile(srcName, destPath) {
11
+ if (existsSync(destPath)) {
12
+ console.log(` ${srcName} already exists skipped`);
17
13
  return;
18
14
  }
19
- const src = resolve(PKG_ROOT, "config.yml");
15
+ const src = resolve(PKG_ROOT, srcName);
20
16
  if (!existsSync(src)) {
21
- console.log(" Warning: bundled config.yml not found — skipping copy");
17
+ console.log(` Warning: bundled ${srcName} not found — skipping`);
22
18
  return;
23
19
  }
24
- mkdirSync(targetDir, { recursive: true });
25
- copyFileSync(src, targetConfig);
26
- console.log(` config.yml copied to ${targetConfig}`);
20
+ mkdirSync(dirname(destPath), { recursive: true });
21
+ copyFileSync(src, destPath);
22
+ console.log(` ${srcName} ${destPath}`);
27
23
  }
28
24
  async function init(args) {
29
25
  const linearKey = args["linear-key"];
30
26
  const notionKey = args["notion-key"];
31
- const isGlobal = !!args.global;
32
27
  const teamId = args["team-id"];
33
28
  const projectId = args["project-id"];
34
29
  const notionPage = args["notion-page"];
35
30
  if (!linearKey) {
36
- throw new Error("Usage: pm-skill init --linear-key <key> [--notion-key <key>] [--global]\n" +
31
+ throw new Error("Usage: npx pm-skill init --linear-key <key> [--notion-key <key>]\n" +
37
32
  " --linear-key Linear API key (required)\n" +
38
33
  " --notion-key Notion API key (optional)\n" +
39
- " --global Save to ~/.pm-skill/ instead of CWD\n" +
40
34
  " --team-id Linear team ID (auto-detected if omitted)\n" +
41
35
  " --project-id Linear project ID (optional)\n" +
42
36
  " --notion-page Notion root page ID (optional)");
43
37
  }
44
- const targetDir = isGlobal ? GLOBAL_DIR : process.cwd();
45
- const targetLabel = isGlobal ? `~/.pm-skill/` : "CWD";
46
- console.log(`=== pm-skill init (${targetLabel}) ===\n`);
38
+ const cwd = process.cwd();
39
+ console.log(`=== pm-skill init ===\n`);
47
40
  // 1. Validate Linear key
48
41
  console.log("[Linear] Validating API key...");
49
42
  const linearUser = await validateLinearKey(linearKey);
@@ -73,7 +66,7 @@ async function init(args) {
73
66
  console.log(` Team: ${match.key} (${match.name})`);
74
67
  }
75
68
  else {
76
- throw new Error(`Team '${teamId}' not found. Run 'pm-skill init --linear-key <key>' to see available teams.`);
69
+ throw new Error(`Team '${teamId}' not found. Run 'npx pm-skill init --linear-key <key>' to see available teams.`);
77
70
  }
78
71
  }
79
72
  // 3. Validate Notion key (optional)
@@ -83,7 +76,7 @@ async function init(args) {
83
76
  console.log(` Authenticated as: ${notionUser.name}`);
84
77
  }
85
78
  // 4. Write .env
86
- console.log(`\n[Config] Writing .env to ${targetLabel}...`);
79
+ console.log("\n[Config] Writing .env...");
87
80
  const envEntries = {
88
81
  LINEAR_API_KEY: linearKey,
89
82
  LINEAR_DEFAULT_TEAM_ID: selectedTeamId,
@@ -94,25 +87,26 @@ async function init(args) {
94
87
  envEntries.NOTION_API_KEY = notionKey;
95
88
  if (notionPage)
96
89
  envEntries.NOTION_ROOT_PAGE_ID = notionPage;
97
- const envPath = writeEnvFile(targetDir, envEntries);
90
+ const envPath = writeEnvFile(cwd, envEntries);
98
91
  console.log(` Written: ${envPath}`);
99
- // 5. Copy config.yml if missing
100
- console.log(`\n[Config] Checking config.yml...`);
101
- copyDefaultConfig(targetDir);
92
+ // 5. Copy config.yml, SKILL.md, AGENTS.md
93
+ console.log("\n[Files] Setting up project files...");
94
+ copyBundledFile("config.yml", resolve(cwd, "config.yml"));
95
+ copyBundledFile("SKILL.md", resolve(cwd, ".claude", "skills", "pm-skill", "SKILL.md"));
96
+ copyBundledFile("AGENTS.md", resolve(cwd, "AGENTS.md"));
102
97
  // 6. Summary
103
98
  console.log(`\n=== Init complete ===`);
104
- console.log(` .env: ${envPath}`);
105
- console.log(` config.yml: ${resolve(targetDir, "config.yml")}`);
106
- console.log(` Linear user: ${linearUser.name}`);
107
- console.log(` Team: ${selectedTeamId}`);
99
+ console.log(` .env: ${envPath}`);
100
+ console.log(` config.yml: ${resolve(cwd, "config.yml")}`);
101
+ console.log(` SKILL.md: ${resolve(cwd, ".claude/skills/pm-skill/SKILL.md")}`);
102
+ console.log(` AGENTS.md: ${resolve(cwd, "AGENTS.md")}`);
103
+ console.log(` Linear: ${linearUser.name} | Team: ${selectedTeamId}`);
108
104
  if (notionKey)
109
- console.log(` Notion: connected`);
105
+ console.log(` Notion: connected`);
110
106
  console.log(`\nNext steps:`);
111
- if (isGlobal) {
112
- console.log(` - Per-project overrides: create .env in your project directory`);
113
- }
114
- console.log(` - Customize config.yml labels/templates for your project`);
115
- console.log(` - Run 'pm-skill setup' to verify label matching`);
107
+ console.log(` - Edit config.yml to match your project's labels/templates`);
108
+ console.log(` - Run 'npx pm-skill setup' to verify label matching`);
109
+ console.log(` - Run 'npx pm-skill setup --sync' to create missing labels in Linear`);
116
110
  }
117
111
  // ── Commands ──
118
112
  async function setup(ctx, args) {
@@ -134,7 +128,7 @@ async function setup(ctx, args) {
134
128
  }
135
129
  // 3. Labels + matching
136
130
  console.log("\n🏷️ Linear labels:");
137
- let teamLabels = await getTeamLabels(ctx.linear, teamId);
131
+ const teamLabels = await getTeamLabels(ctx.linear, teamId);
138
132
  for (const label of teamLabels) {
139
133
  console.log(` ${label.name} | ${label.id}`);
140
134
  }
@@ -165,7 +159,7 @@ async function setup(ctx, args) {
165
159
  console.log("\nLabels synced successfully.");
166
160
  }
167
161
  else if (missingLabels.length > 0) {
168
- console.log(`\n💡 ${missingLabels.length} label(s) missing. Run 'pm-skill setup --sync' to create them.`);
162
+ console.log(`\n💡 ${missingLabels.length} label(s) missing. Run 'npx pm-skill setup --sync' to create them.`);
169
163
  }
170
164
  else {
171
165
  console.log("\n✅ All config labels matched.");
@@ -192,13 +186,13 @@ async function setup(ctx, args) {
192
186
  }
193
187
  else {
194
188
  console.log(" ⚠️ NOTION_API_KEY not set — Notion features disabled");
195
- console.log(" Set it via: pm-skill init --notion-key <key> --global");
189
+ console.log(" Run 'npx pm-skill init --linear-key <key> --notion-key <key>' to set up");
196
190
  }
197
191
  }
198
192
  async function startFeature(ctx, args) {
199
193
  const title = args._[0];
200
194
  if (!title) {
201
- throw new Error("사용법: start-feature <제목>");
195
+ throw new Error("Usage: pm-skill start-feature <title>");
202
196
  }
203
197
  const tmpl = getTemplate(ctx.config, "feature");
204
198
  const teamLabels = await getTeamLabels(ctx.linear, ctx.env.LINEAR_DEFAULT_TEAM_ID);
@@ -207,7 +201,6 @@ async function startFeature(ctx, args) {
207
201
  const priority = tmpl.linear_priority
208
202
  ? resolvePriority(ctx.config, tmpl.linear_priority)
209
203
  : undefined;
210
- // 1. Linear issue
211
204
  const issue = await createIssue(ctx.linear, {
212
205
  teamId: ctx.env.LINEAR_DEFAULT_TEAM_ID,
213
206
  title,
@@ -215,25 +208,21 @@ async function startFeature(ctx, args) {
215
208
  labelIds,
216
209
  projectId: ctx.env.LINEAR_DEFAULT_PROJECT_ID,
217
210
  });
218
- const issueId = issue.identifier;
219
- const issueUrl = issue.url;
220
- console.log(`[Linear] 이슈 생성: ${issueId} — ${issueUrl}`);
221
- // 2. Notion page
211
+ console.log(`[Linear] Issue created: ${issue.identifier} — ${issue.url}`);
222
212
  if (!ctx.notion || !ctx.env.NOTION_ROOT_PAGE_ID) {
223
- console.log("[Notion] Notion 설정 없음 페이지 생성 생략");
213
+ console.log("[Notion] Not configuredskipping page creation");
224
214
  return;
225
215
  }
226
- const page = await createTemplatedPage(ctx.notion, ctx.env.NOTION_ROOT_PAGE_ID, tmpl.notion_template, title, issueUrl);
227
- console.log(`[Notion] 페이지 생성: ${page.url}`);
228
- // 3. Link Linear → Notion
216
+ const page = await createTemplatedPage(ctx.notion, ctx.env.NOTION_ROOT_PAGE_ID, tmpl.notion_template, title, issue.url);
217
+ console.log(`[Notion] Page created: ${page.url}`);
229
218
  await createAttachment(ctx.linear, issue.id, page.url, `${title} — PRD`);
230
- console.log(`[Link] Linear ↔ Notion 연결 완료`);
231
- console.log(`\n✅ 기능 시작: ${issueId} | Notion: ${page.url}`);
219
+ console.log(`[Link] Linear ↔ Notion linked`);
220
+ console.log(`\n✅ Feature started: ${issue.identifier} | Notion: ${page.url}`);
232
221
  }
233
222
  async function reportBug(ctx, args) {
234
223
  const title = args._[0];
235
224
  if (!title) {
236
- throw new Error("사용법: report-bug <제목> [--severity high]");
225
+ throw new Error("Usage: pm-skill report-bug <title> [--severity high]");
237
226
  }
238
227
  const severity = args.severity ?? "medium";
239
228
  const priority = resolveSeverity(ctx.config, severity);
@@ -241,7 +230,6 @@ async function reportBug(ctx, args) {
241
230
  const teamLabels = await getTeamLabels(ctx.linear, ctx.env.LINEAR_DEFAULT_TEAM_ID);
242
231
  const configLabels = tmpl.linear_labels.map((lid) => validateLabel(ctx.config, lid).name);
243
232
  const labelIds = resolveLabels(configLabels, teamLabels);
244
- // 1. Linear issue
245
233
  const issue = await createIssue(ctx.linear, {
246
234
  teamId: ctx.env.LINEAR_DEFAULT_TEAM_ID,
247
235
  title,
@@ -249,43 +237,37 @@ async function reportBug(ctx, args) {
249
237
  labelIds,
250
238
  projectId: ctx.env.LINEAR_DEFAULT_PROJECT_ID,
251
239
  });
252
- const issueId = issue.identifier;
253
- const issueUrl = issue.url;
254
- console.log(`[Linear] 버그 이슈 생성: ${issueId} (severity: ${severity}) — ${issueUrl}`);
255
- // 2. Notion
240
+ console.log(`[Linear] Bug created: ${issue.identifier} (severity: ${severity}) — ${issue.url}`);
256
241
  if (!ctx.notion) {
257
- console.log("[Notion] Notion 설정 없음 문서 생성 생략");
242
+ console.log("[Notion] Not configuredskipping");
258
243
  return;
259
244
  }
260
245
  let notionUrl;
261
246
  if (ctx.env.NOTION_BUG_DB_ID) {
262
- // DB 엔트리
263
247
  const entry = await createDatabaseEntry(ctx.notion, ctx.env.NOTION_BUG_DB_ID, {
264
248
  Name: { title: [{ text: { content: title } }] },
265
249
  });
266
250
  notionUrl = entry.url;
267
- console.log(`[Notion] 버그 DB 엔트리 생성: ${notionUrl}`);
251
+ console.log(`[Notion] Bug DB entry: ${notionUrl}`);
268
252
  }
269
253
  else if (ctx.env.NOTION_ROOT_PAGE_ID) {
270
- // 페이지
271
- const page = await createTemplatedPage(ctx.notion, ctx.env.NOTION_ROOT_PAGE_ID, tmpl.notion_template, title, issueUrl, severity);
254
+ const page = await createTemplatedPage(ctx.notion, ctx.env.NOTION_ROOT_PAGE_ID, tmpl.notion_template, title, issue.url, severity);
272
255
  notionUrl = page.url;
273
- console.log(`[Notion] 버그리포트 페이지 생성: ${notionUrl}`);
256
+ console.log(`[Notion] Bug report page: ${notionUrl}`);
274
257
  }
275
258
  else {
276
- console.log("[Notion] NOTION_ROOT_PAGE_ID 미설정문서 생성 생략");
259
+ console.log("[Notion] NOTION_ROOT_PAGE_ID not set skipping");
277
260
  return;
278
261
  }
279
- // 3. Link
280
262
  await createAttachment(ctx.linear, issue.id, notionUrl, `${title} — Bug Report`);
281
- console.log(`[Link] Linear ↔ Notion 연결 완료`);
282
- console.log(`\n✅ 버그 리포트: ${issueId} | Notion: ${notionUrl}`);
263
+ console.log(`[Link] Linear ↔ Notion linked`);
264
+ console.log(`\n✅ Bug reported: ${issue.identifier} | Notion: ${notionUrl}`);
283
265
  }
284
266
  async function addTask(ctx, args) {
285
267
  const parentIdentifier = args._[0];
286
268
  const title = args._[1];
287
269
  if (!parentIdentifier || !title) {
288
- throw new Error("사용법: add-task <부모이슈> <제목>");
270
+ throw new Error("Usage: pm-skill add-task <parent-issue> <title>");
289
271
  }
290
272
  const parent = await getIssue(ctx.linear, parentIdentifier);
291
273
  const child = await createIssue(ctx.linear, {
@@ -294,33 +276,33 @@ async function addTask(ctx, args) {
294
276
  parentId: parent.id,
295
277
  projectId: ctx.env.LINEAR_DEFAULT_PROJECT_ID,
296
278
  });
297
- console.log(`✅ 하위 이슈 생성: ${child.identifier} (부모: ${parent.identifier})`);
279
+ console.log(`✅ Sub-issue created: ${child.identifier} (parent: ${parent.identifier})`);
298
280
  }
299
281
  async function relate(ctx, args) {
300
282
  const id1 = args._[0];
301
283
  const id2 = args._[1];
302
284
  if (!id1 || !id2) {
303
- throw new Error("사용법: relate <이슈1> <이슈2> [--type related]");
285
+ throw new Error("Usage: pm-skill relate <issue1> <issue2> [--type related]");
304
286
  }
305
287
  const type = args.type ?? "related";
306
288
  if (type !== "related" && type !== "similar") {
307
- throw new Error(`relate 커맨드는 'related' 또는 'similar' 타입만 지원합니다. blocks 관계는 'block' 커맨드를 사용하세요.`);
289
+ throw new Error(`relate only supports 'related' or 'similar'. Use 'block' command for blocking relationships.`);
308
290
  }
309
291
  const issue1 = await getIssue(ctx.linear, id1);
310
292
  const issue2 = await getIssue(ctx.linear, id2);
311
293
  await createRelation(ctx.linear, issue1.id, issue2.id, type);
312
- console.log(`✅ ${id1} ↔ ${id2} (${type}) 관계 생성 완료`);
294
+ console.log(`✅ ${id1} ↔ ${id2} (${type}) linked`);
313
295
  }
314
296
  async function block(ctx, args) {
315
297
  const id1 = args._[0];
316
298
  const id2 = args._[1];
317
299
  if (!id1 || !id2) {
318
- throw new Error("사용법: block <선행이슈> <후행이슈>");
300
+ throw new Error("Usage: pm-skill block <blocker> <blocked>");
319
301
  }
320
302
  const issue1 = await getIssue(ctx.linear, id1);
321
303
  const issue2 = await getIssue(ctx.linear, id2);
322
304
  await createRelation(ctx.linear, issue1.id, issue2.id, "blocks");
323
- console.log(`✅ ${id1} ${id2}를 선행합니다 (blocks)`);
305
+ console.log(`✅ ${id1} blocks ${id2}`);
324
306
  }
325
307
  async function attachDoc(ctx, args) {
326
308
  const identifier = args._[0];
@@ -328,17 +310,17 @@ async function attachDoc(ctx, args) {
328
310
  const title = args.title;
329
311
  const type = args.type;
330
312
  if (!identifier || !url || !title || !type) {
331
- throw new Error('사용법: attach-doc <이슈> --url "URL" --title "제목" --type <유형>');
313
+ throw new Error('Usage: pm-skill attach-doc <issue> --url "URL" --title "Title" --type <type>');
332
314
  }
333
315
  validateDocType(ctx.config, type);
334
316
  const issue = await getIssue(ctx.linear, identifier);
335
317
  await createAttachment(ctx.linear, issue.id, url, title, type);
336
- console.log(`✅ ${identifier} 문서 첨부: "${title}" (${type}) — ${url}`);
318
+ console.log(`✅ ${identifier}: attached "${title}" (${type}) — ${url}`);
337
319
  }
338
320
  async function get(ctx, args) {
339
321
  const identifier = args._[0];
340
322
  if (!identifier) {
341
- throw new Error("사용법: get <이슈>");
323
+ throw new Error("Usage: pm-skill get <issue>");
342
324
  }
343
325
  const detail = await getIssueDetail(ctx.linear, identifier);
344
326
  const { issue, children, relations, attachments } = detail;
@@ -346,26 +328,26 @@ async function get(ctx, args) {
346
328
  const labels = await issue.labels();
347
329
  const labelNames = labels.nodes.map((l) => l.name).join(", ");
348
330
  console.log(`\n${issue.identifier}: ${issue.title}`);
349
- console.log(`상태: ${state?.name ?? "?"} | 우선순위: ${issue.priority ?? "?"} | 라벨: ${labelNames || "없음"}`);
331
+ console.log(`State: ${state?.name ?? "?"} | Priority: ${issue.priority ?? "?"} | Labels: ${labelNames || "none"}`);
350
332
  if (children.length > 0) {
351
- console.log("\n하위 이슈:");
333
+ console.log("\nSub-issues:");
352
334
  for (const child of children) {
353
335
  const childState = await child.state;
354
336
  console.log(` ${child.identifier}: ${child.title} (${childState?.name ?? "?"})`);
355
337
  }
356
338
  }
357
339
  if (relations.length > 0) {
358
- console.log("\n관계:");
340
+ console.log("\nRelations:");
359
341
  for (const rel of relations) {
360
342
  const arrow = rel.type === "blocks" ? "→ blocks" : "↔ related";
361
343
  console.log(` ${arrow} ${rel.issue.identifier} (${rel.issue.title})`);
362
344
  }
363
345
  }
364
346
  if (attachments.length > 0) {
365
- console.log("\n첨부 문서:");
347
+ console.log("\nAttachments:");
366
348
  for (const att of attachments) {
367
349
  const typeLabel = att.subtitle ? ` (${att.subtitle})` : "";
368
- console.log(` 📄 ${att.title}${typeLabel} — ${att.url}`);
350
+ console.log(` ${att.title}${typeLabel} — ${att.url}`);
369
351
  }
370
352
  }
371
353
  }
@@ -384,26 +366,25 @@ const COMMANDS = {
384
366
  async function main() {
385
367
  const args = minimist(process.argv.slice(2), {
386
368
  string: ["severity", "type", "url", "title", "linear-key", "notion-key", "team-id", "project-id", "notion-page"],
387
- boolean: ["global", "sync", "version"],
369
+ boolean: ["sync", "version"],
388
370
  alias: { s: "severity", t: "type" },
389
371
  });
390
372
  // --version
391
373
  if (args.version) {
392
- const { readFileSync } = await import("fs");
393
374
  const pkg = JSON.parse(readFileSync(resolve(PKG_ROOT, "package.json"), "utf-8"));
394
375
  console.log(`pm-skill v${pkg.version}`);
395
376
  return;
396
377
  }
397
378
  const command = args._[0];
398
- args._ = args._.slice(1); // command 제거, 나머지가 positional args
379
+ args._ = args._.slice(1);
399
380
  if (!command || command === "help") {
400
381
  console.log(`pm-skill — Structured project management CLI (Linear + Notion)
401
382
 
402
- Usage: pm-skill <command> [args] [flags]
383
+ Usage: npx pm-skill <command> [args] [flags]
403
384
 
404
385
  Commands:
405
- init --linear-key K [--notion-key K] [--global]
406
- Initialize config & validate API keys
386
+ init --linear-key K [--notion-key K]
387
+ Initialize project (validates keys, creates .env, config.yml, SKILL.md, AGENTS.md)
407
388
  setup [--sync] Verify config & label matching (--sync creates missing labels)
408
389
  start-feature <title> Start feature (Linear issue + Notion PRD)
409
390
  report-bug <title> [--severity S] File bug report (severity: urgent/high/medium/low)
@@ -414,8 +395,9 @@ Commands:
414
395
  Attach document (type: source-of-truth/issue-tracking/domain-knowledge)
415
396
  get <issue> Show issue details
416
397
  help Show this help
398
+ --version Show version
417
399
 
418
- Config lookup: CWD/.env + ~/.pm-skill/.env (both loaded, CWD wins)`);
400
+ All config is per-project (CWD). Run 'npx pm-skill init' in each project.`);
419
401
  return;
420
402
  }
421
403
  // init runs independently — no env/config validation
@@ -428,11 +410,8 @@ Config lookup: CWD/.env + ~/.pm-skill/.env (both loaded, CWD wins)`);
428
410
  const available = ["init", ...Object.keys(COMMANDS)].join(", ");
429
411
  throw new Error(`Unknown command: '${command}'\nAvailable: ${available}`);
430
412
  }
431
- // Validate env
432
413
  const env = validateEnv(command);
433
- // Load & validate config
434
414
  const config = loadConfig();
435
- // Build context
436
415
  const linear = getLinearClient(env.LINEAR_API_KEY);
437
416
  const notion = env.NOTION_API_KEY
438
417
  ? getNotionClient(env.NOTION_API_KEY)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pm-skill",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Structured project management CLI — Linear + Notion integration for AI coding assistants (Claude Code, Codex)",
5
5
  "type": "module",
6
6
  "bin": {