pm-skill 1.0.0 → 1.0.2

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/dist/linear.d.ts CHANGED
@@ -38,6 +38,13 @@ export declare function getIssue(client: LinearClient, identifier: string): Prom
38
38
  export declare function getIssueDetail(client: LinearClient, identifier: string): Promise<IssueDetail>;
39
39
  export declare function createRelation(client: LinearClient, issueId: string, relatedIssueId: string, type: "blocks" | "related" | "similar"): Promise<void>;
40
40
  export declare function createAttachment(client: LinearClient, issueId: string, url: string, title: string, subtitle?: string): Promise<void>;
41
+ export declare function createLabel(client: LinearClient, teamId: string, name: string, opts?: {
42
+ description?: string;
43
+ color?: string;
44
+ }): Promise<{
45
+ id: string;
46
+ name: string;
47
+ }>;
41
48
  export declare function getTeams(client: LinearClient): Promise<Team[]>;
42
49
  export declare function getTeamStates(client: LinearClient, teamId: string): Promise<WorkflowState[]>;
43
50
  export declare function getTeamLabels(client: LinearClient, teamId: string): Promise<IssueLabel[]>;
package/dist/linear.js CHANGED
@@ -102,6 +102,20 @@ export async function createAttachment(client, issueId, url, title, subtitle) {
102
102
  subtitle,
103
103
  });
104
104
  }
105
+ // ── Label Creation ──
106
+ export async function createLabel(client, teamId, name, opts) {
107
+ const payload = await client.createIssueLabel({
108
+ teamId,
109
+ name,
110
+ description: opts?.description,
111
+ color: opts?.color,
112
+ });
113
+ const label = await payload.issueLabel;
114
+ if (!label) {
115
+ throw new Error(`Failed to create label '${name}'.`);
116
+ }
117
+ return { id: label.id, name: label.name };
118
+ }
105
119
  // ── Team / Labels / States ──
106
120
  export async function getTeams(client) {
107
121
  const conn = await client.teams();
package/dist/workflows.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import minimist from "minimist";
3
3
  import { validateEnv, writeEnvFile, GLOBAL_DIR } from "./env.js";
4
4
  import { loadConfig, getTemplate, resolvePriority, resolveSeverity, validateDocType, validateLabel, } from "./config.js";
5
- import { getLinearClient, validateLinearKey, createIssue, getIssue, getIssueDetail, createRelation, createAttachment, getTeams, getTeamStates, getTeamLabels, resolveLabels, } from "./linear.js";
5
+ import { getLinearClient, validateLinearKey, createIssue, getIssue, getIssueDetail, createRelation, createAttachment, createLabel, getTeams, getTeamStates, getTeamLabels, resolveLabels, } from "./linear.js";
6
6
  import { getNotionClient, createTemplatedPage, createDatabaseEntry, validateNotionKey, } from "./notion.js";
7
7
  // ── Init (runs before context — no env/config validation needed) ──
8
8
  import { existsSync, copyFileSync, mkdirSync } from "fs";
@@ -115,51 +115,85 @@ async function init(args) {
115
115
  console.log(` - Run 'pm-skill setup' to verify label matching`);
116
116
  }
117
117
  // ── Commands ──
118
- async function setup(ctx) {
119
- console.log("=== PM Skill Setup ===\n");
118
+ async function setup(ctx, args) {
119
+ const sync = !!args.sync;
120
+ console.log(`=== PM Skill Setup${sync ? " (--sync)" : ""} ===\n`);
120
121
  // 1. Teams
121
122
  const teams = await getTeams(ctx.linear);
122
- console.log("📋 Linear 팀 목록:");
123
+ console.log("📋 Linear teams:");
123
124
  for (const team of teams) {
124
- const marker = team.id === ctx.env.LINEAR_DEFAULT_TEAM_ID ? " ← 현재 설정" : "";
125
+ const marker = team.id === ctx.env.LINEAR_DEFAULT_TEAM_ID ? " ← current" : "";
125
126
  console.log(` ${team.key} | ${team.name} | ${team.id}${marker}`);
126
127
  }
127
128
  // 2. States
128
129
  const teamId = ctx.env.LINEAR_DEFAULT_TEAM_ID;
129
- console.log(`\n📊 상태 목록 (${teamId}):`);
130
+ console.log(`\n📊 Workflow states (${teamId}):`);
130
131
  const states = await getTeamStates(ctx.linear, teamId);
131
132
  for (const state of states) {
132
133
  console.log(` ${state.name} (${state.type}) | ${state.id}`);
133
134
  }
134
135
  // 3. Labels + matching
135
- console.log("\n🏷️ Linear 라벨 목록:");
136
- const teamLabels = await getTeamLabels(ctx.linear, teamId);
136
+ console.log("\n🏷️ Linear labels:");
137
+ let teamLabels = await getTeamLabels(ctx.linear, teamId);
137
138
  for (const label of teamLabels) {
138
139
  console.log(` ${label.name} | ${label.id}`);
139
140
  }
140
- // 4. Config label matching
141
- console.log("\n🔗 Config ↔ Linear 라벨 매칭:");
141
+ // 4. Config label matching + sync
142
+ console.log("\n🔗 Config ↔ Linear label matching:");
142
143
  const linearLabelMap = new Map(teamLabels.map((l) => [l.name.toLowerCase(), l]));
144
+ const missingLabels = [];
143
145
  for (const configLabel of ctx.config.labels) {
144
146
  const match = linearLabelMap.get(configLabel.name.toLowerCase());
145
147
  if (match) {
146
148
  console.log(` ✅ ${configLabel.id} (${configLabel.name}) → ${match.id}`);
147
149
  }
148
150
  else {
149
- console.log(` ⚠️ ${configLabel.id} (${configLabel.name}) → 매칭 없음! Linear에 '${configLabel.name}' 라벨을 생성하세요.`);
151
+ missingLabels.push(configLabel);
152
+ console.log(` ⚠️ ${configLabel.id} (${configLabel.name}) → not found in Linear`);
150
153
  }
151
154
  }
152
- // 5. .env guide
153
- console.log("\n📝 .env 설정 안내:");
154
- console.log(" LINEAR_API_KEY=<위에서 사용 중인 키>");
155
- console.log(` LINEAR_DEFAULT_TEAM_ID=${teamId}`);
156
- if (ctx.env.LINEAR_DEFAULT_PROJECT_ID) {
157
- console.log(` LINEAR_DEFAULT_PROJECT_ID=${ctx.env.LINEAR_DEFAULT_PROJECT_ID}`);
155
+ // 5. Sync missing labels
156
+ if (missingLabels.length > 0 && sync) {
157
+ console.log(`\n🔄 Creating ${missingLabels.length} missing label(s) in Linear...`);
158
+ for (const configLabel of missingLabels) {
159
+ const created = await createLabel(ctx.linear, teamId, configLabel.name, {
160
+ description: configLabel.description,
161
+ color: configLabel.color,
162
+ });
163
+ console.log(` ✅ Created: ${configLabel.name} → ${created.id}`);
164
+ }
165
+ console.log("\nLabels synced successfully.");
166
+ }
167
+ else if (missingLabels.length > 0) {
168
+ console.log(`\n💡 ${missingLabels.length} label(s) missing. Run 'pm-skill setup --sync' to create them.`);
158
169
  }
159
170
  else {
160
- console.log(" LINEAR_DEFAULT_PROJECT_ID=<Linear 프로젝트 ID (선택)>");
171
+ console.log("\n✅ All config labels matched.");
172
+ }
173
+ // 6. Notion connection check
174
+ console.log("\n📓 Notion:");
175
+ if (ctx.env.NOTION_API_KEY) {
176
+ try {
177
+ const notionInfo = await validateNotionKey(ctx.env.NOTION_API_KEY);
178
+ console.log(` ✅ Connected: ${notionInfo.name}`);
179
+ if (ctx.env.NOTION_ROOT_PAGE_ID) {
180
+ console.log(` Root page: ${ctx.env.NOTION_ROOT_PAGE_ID}`);
181
+ }
182
+ else {
183
+ console.log(` ⚠️ NOTION_ROOT_PAGE_ID not set — page creation will be skipped`);
184
+ }
185
+ if (ctx.env.NOTION_BUG_DB_ID) {
186
+ console.log(` Bug DB: ${ctx.env.NOTION_BUG_DB_ID}`);
187
+ }
188
+ }
189
+ catch {
190
+ console.log(" ❌ Notion API key is invalid. Check https://www.notion.so/my-integrations");
191
+ }
192
+ }
193
+ else {
194
+ console.log(" ⚠️ NOTION_API_KEY not set — Notion features disabled");
195
+ console.log(" Set it via: pm-skill init --notion-key <key> --global");
161
196
  }
162
- console.log("\nNotion 설정은 https://www.notion.so/my-integrations 에서 키를 발급하세요.");
163
197
  }
164
198
  async function startFeature(ctx, args) {
165
199
  const title = args._[0];
@@ -337,7 +371,7 @@ async function get(ctx, args) {
337
371
  }
338
372
  // ── Command Registry ──
339
373
  const COMMANDS = {
340
- setup: (ctx) => setup(ctx),
374
+ setup: (ctx, args) => setup(ctx, args),
341
375
  "start-feature": startFeature,
342
376
  "report-bug": reportBug,
343
377
  "add-task": addTask,
@@ -350,9 +384,16 @@ const COMMANDS = {
350
384
  async function main() {
351
385
  const args = minimist(process.argv.slice(2), {
352
386
  string: ["severity", "type", "url", "title", "linear-key", "notion-key", "team-id", "project-id", "notion-page"],
353
- boolean: ["global"],
387
+ boolean: ["global", "sync", "version"],
354
388
  alias: { s: "severity", t: "type" },
355
389
  });
390
+ // --version
391
+ if (args.version) {
392
+ const { readFileSync } = await import("fs");
393
+ const pkg = JSON.parse(readFileSync(resolve(PKG_ROOT, "package.json"), "utf-8"));
394
+ console.log(`pm-skill v${pkg.version}`);
395
+ return;
396
+ }
356
397
  const command = args._[0];
357
398
  args._ = args._.slice(1); // command 제거, 나머지가 positional args
358
399
  if (!command || command === "help") {
@@ -363,7 +404,7 @@ Usage: pm-skill <command> [args] [flags]
363
404
  Commands:
364
405
  init --linear-key K [--notion-key K] [--global]
365
406
  Initialize config & validate API keys
366
- setup Verify Linear/Notion connection & show config
407
+ setup [--sync] Verify config & label matching (--sync creates missing labels)
367
408
  start-feature <title> Start feature (Linear issue + Notion PRD)
368
409
  report-bug <title> [--severity S] File bug report (severity: urgent/high/medium/low)
369
410
  add-task <parent> <title> Add sub-task to an issue
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pm-skill",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Structured project management CLI — Linear + Notion integration for AI coding assistants (Claude Code, Codex)",
5
5
  "type": "module",
6
6
  "bin": {