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 +7 -0
- package/dist/linear.js +14 -0
- package/dist/workflows.js +63 -22
- package/package.json +1 -1
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
|
-
|
|
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📊
|
|
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
|
-
|
|
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
|
-
|
|
151
|
+
missingLabels.push(configLabel);
|
|
152
|
+
console.log(` ⚠️ ${configLabel.id} (${configLabel.name}) → not found in Linear`);
|
|
150
153
|
}
|
|
151
154
|
}
|
|
152
|
-
// 5.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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("
|
|
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
|
|
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
|