github-labels-template 0.7.0 → 0.8.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/dist/index.js +187 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { defineCommand as
|
|
4
|
+
import { defineCommand as defineCommand7, runMain } from "citty";
|
|
5
5
|
|
|
6
6
|
// src/commands/apply.ts
|
|
7
7
|
import { defineCommand } from "citty";
|
|
@@ -621,21 +621,24 @@ var CATEGORY_NAMES = {
|
|
|
621
621
|
resolution: "Resolution",
|
|
622
622
|
area: "Area"
|
|
623
623
|
};
|
|
624
|
-
function buildSystemPrompt(category, count) {
|
|
624
|
+
function buildSystemPrompt(category, count, attempt = 1) {
|
|
625
625
|
const categoryTitle = CATEGORY_NAMES[category] ?? category;
|
|
626
626
|
const existingLabels = labels_default[category] ?? [];
|
|
627
627
|
const existingList = existingLabels.map((l) => ` - "${l.name}" (${l.color}) — ${l.description}`).join(`
|
|
628
628
|
`);
|
|
629
|
+
const variationHint = attempt > 1 ? `IMPORTANT: This is attempt #${attempt}. You MUST generate completely different label names, colors, and descriptions than any previous suggestions. Be creative and explore new angles.` : null;
|
|
629
630
|
return [
|
|
630
|
-
`You are a GitHub label generator. Generate exactly ${count} label suggestions for the "${categoryTitle}" category.`,
|
|
631
|
+
`You are a GitHub label generator. The user will describe the kind of label they need. Generate exactly ${count} label suggestions for the "${categoryTitle}" category that DIRECTLY match what the user is asking for.`,
|
|
631
632
|
"",
|
|
633
|
+
"CRITICAL: Every suggestion MUST be relevant to the user's description. Do NOT generate generic or unrelated labels. Focus on what the user specifically asked for.",
|
|
634
|
+
...variationHint ? ["", variationHint, ""] : [""],
|
|
632
635
|
"Each label must follow this exact JSON format:",
|
|
633
636
|
"[",
|
|
634
637
|
` { "name": "label-name", "color": "hex123", "description": "[${categoryTitle}] Description text [scope]" }`,
|
|
635
638
|
"]",
|
|
636
639
|
"",
|
|
637
640
|
"Rules:",
|
|
638
|
-
"- name: lowercase, concise (1-3 words), use spaces for multi-word names",
|
|
641
|
+
"- name: lowercase, concise (1-3 words), use spaces for multi-word names, must reflect the user's request",
|
|
639
642
|
"- color: 6-character hex without #, choose colors that are visually distinct from existing labels",
|
|
640
643
|
`- description: MUST start with [${categoryTitle}] and end with [issues], [PRs], or [issues, PRs]`,
|
|
641
644
|
"- Do NOT duplicate any of these existing labels:",
|
|
@@ -645,12 +648,17 @@ function buildSystemPrompt(category, count) {
|
|
|
645
648
|
].join(`
|
|
646
649
|
`);
|
|
647
650
|
}
|
|
648
|
-
function buildUserPrompt(description, refinement) {
|
|
651
|
+
function buildUserPrompt(description, refinement, attempt = 1) {
|
|
649
652
|
let prompt = `I need a label for: ${description}`;
|
|
650
653
|
if (refinement) {
|
|
651
654
|
prompt += `
|
|
652
655
|
|
|
653
656
|
Refinement feedback: ${refinement}`;
|
|
657
|
+
}
|
|
658
|
+
if (attempt > 1) {
|
|
659
|
+
prompt += `
|
|
660
|
+
|
|
661
|
+
Generate different suggestions from previous attempts. This is attempt #${attempt}, so provide fresh and unique alternatives.`;
|
|
654
662
|
}
|
|
655
663
|
return prompt;
|
|
656
664
|
}
|
|
@@ -683,13 +691,13 @@ function parseLabelsResponse(text) {
|
|
|
683
691
|
});
|
|
684
692
|
}
|
|
685
693
|
async function generateLabels(options) {
|
|
686
|
-
const { category, description, count = 3, refinement, model } = options;
|
|
694
|
+
const { category, description, count = 3, refinement, model, attempt = 1 } = options;
|
|
687
695
|
const client = new CopilotClient;
|
|
688
696
|
await client.start();
|
|
689
697
|
try {
|
|
690
698
|
const sessionConfig = {
|
|
691
699
|
systemMessage: {
|
|
692
|
-
content: buildSystemPrompt(category, count)
|
|
700
|
+
content: buildSystemPrompt(category, count, attempt)
|
|
693
701
|
}
|
|
694
702
|
};
|
|
695
703
|
if (model) {
|
|
@@ -697,7 +705,7 @@ async function generateLabels(options) {
|
|
|
697
705
|
}
|
|
698
706
|
const session = await client.createSession(sessionConfig);
|
|
699
707
|
try {
|
|
700
|
-
const userPrompt = buildUserPrompt(description, refinement);
|
|
708
|
+
const userPrompt = buildUserPrompt(description, refinement, attempt);
|
|
701
709
|
const response = await session.sendAndWait({ content: userPrompt });
|
|
702
710
|
if (!response || !response.data?.content) {
|
|
703
711
|
throw new Error("No response received from Copilot");
|
|
@@ -818,6 +826,7 @@ var generate_default = defineCommand3({
|
|
|
818
826
|
});
|
|
819
827
|
let selectedLabel = null;
|
|
820
828
|
let refinement;
|
|
829
|
+
let attempt = 1;
|
|
821
830
|
while (!selectedLabel) {
|
|
822
831
|
info(refinement ? "Regenerating with your feedback..." : "Generating label suggestions...");
|
|
823
832
|
let suggestions;
|
|
@@ -827,7 +836,8 @@ var generate_default = defineCommand3({
|
|
|
827
836
|
description,
|
|
828
837
|
count: 3,
|
|
829
838
|
refinement,
|
|
830
|
-
model: args.model
|
|
839
|
+
model: args.model,
|
|
840
|
+
attempt
|
|
831
841
|
});
|
|
832
842
|
} catch (err) {
|
|
833
843
|
error(`Failed to generate labels: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -837,6 +847,7 @@ var generate_default = defineCommand3({
|
|
|
837
847
|
});
|
|
838
848
|
if (retry) {
|
|
839
849
|
refinement = undefined;
|
|
850
|
+
attempt = 1;
|
|
840
851
|
continue;
|
|
841
852
|
}
|
|
842
853
|
return;
|
|
@@ -873,10 +884,12 @@ var generate_default = defineCommand3({
|
|
|
873
884
|
message: "What would you like to change?",
|
|
874
885
|
validate: (value) => value.trim().length > 0 || "Please provide feedback."
|
|
875
886
|
});
|
|
887
|
+
attempt++;
|
|
876
888
|
continue;
|
|
877
889
|
}
|
|
878
890
|
if (choice === "regenerate") {
|
|
879
891
|
refinement = undefined;
|
|
892
|
+
attempt++;
|
|
880
893
|
continue;
|
|
881
894
|
}
|
|
882
895
|
const pickIndex = parseInt(choice.replace("pick:", ""), 10);
|
|
@@ -1063,13 +1076,167 @@ var list_default = defineCommand5({
|
|
|
1063
1076
|
}
|
|
1064
1077
|
});
|
|
1065
1078
|
|
|
1079
|
+
// src/commands/check.ts
|
|
1080
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
1081
|
+
import pc7 from "picocolors";
|
|
1082
|
+
function statusIcon(status) {
|
|
1083
|
+
if (status === "match")
|
|
1084
|
+
return pc7.green("✔");
|
|
1085
|
+
if (status === "missing")
|
|
1086
|
+
return pc7.red("✘");
|
|
1087
|
+
return pc7.yellow("~");
|
|
1088
|
+
}
|
|
1089
|
+
function statusLabel(status) {
|
|
1090
|
+
switch (status) {
|
|
1091
|
+
case "match":
|
|
1092
|
+
return pc7.dim("match");
|
|
1093
|
+
case "color-mismatch":
|
|
1094
|
+
return pc7.yellow("color mismatch");
|
|
1095
|
+
case "desc-mismatch":
|
|
1096
|
+
return pc7.yellow("description mismatch");
|
|
1097
|
+
case "both-mismatch":
|
|
1098
|
+
return pc7.yellow("color + description mismatch");
|
|
1099
|
+
case "missing":
|
|
1100
|
+
return pc7.red("missing");
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function isFailing(status, strict) {
|
|
1104
|
+
if (status === "missing")
|
|
1105
|
+
return true;
|
|
1106
|
+
if (strict && status !== "match")
|
|
1107
|
+
return true;
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
var check_default = defineCommand6({
|
|
1111
|
+
meta: {
|
|
1112
|
+
name: "check",
|
|
1113
|
+
description: "Check if a repository is using the Clean Label template"
|
|
1114
|
+
},
|
|
1115
|
+
args: {
|
|
1116
|
+
repo: {
|
|
1117
|
+
type: "string",
|
|
1118
|
+
alias: "r",
|
|
1119
|
+
description: "Target repository (owner/repo). Defaults to current repo."
|
|
1120
|
+
},
|
|
1121
|
+
category: {
|
|
1122
|
+
type: "string",
|
|
1123
|
+
alias: "c",
|
|
1124
|
+
description: 'Check specific category(ies) only. Comma-separated (e.g., --category "type,status")'
|
|
1125
|
+
},
|
|
1126
|
+
strict: {
|
|
1127
|
+
type: "boolean",
|
|
1128
|
+
alias: "s",
|
|
1129
|
+
default: false,
|
|
1130
|
+
description: "Strict mode — also flag labels with mismatched color or description"
|
|
1131
|
+
}
|
|
1132
|
+
},
|
|
1133
|
+
async run({ args }) {
|
|
1134
|
+
if (!await checkGhInstalled()) {
|
|
1135
|
+
error("gh CLI is not installed. Install it from https://cli.github.com");
|
|
1136
|
+
process.exit(1);
|
|
1137
|
+
}
|
|
1138
|
+
if (!await checkGhAuth()) {
|
|
1139
|
+
error("Not authenticated. Run `gh auth login` first.");
|
|
1140
|
+
process.exit(1);
|
|
1141
|
+
}
|
|
1142
|
+
const repo = args.repo || await detectRepo();
|
|
1143
|
+
if (!repo) {
|
|
1144
|
+
error("Could not detect repository. Use --repo <owner/repo> or run inside a git repo.");
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}
|
|
1147
|
+
const strict = args.strict ?? false;
|
|
1148
|
+
info(`Target: ${repo}`);
|
|
1149
|
+
if (strict)
|
|
1150
|
+
info("Mode: strict (name + color + description)");
|
|
1151
|
+
const repoLabels = await listLabelsDetailed(repo);
|
|
1152
|
+
const repoMap = new Map(repoLabels.map((l) => [l.name.toLowerCase(), l]));
|
|
1153
|
+
const { entries: templateEntries } = filterLabels(labels_default, {
|
|
1154
|
+
category: args.category
|
|
1155
|
+
});
|
|
1156
|
+
const results = [];
|
|
1157
|
+
for (const [category, categoryLabels] of templateEntries) {
|
|
1158
|
+
for (const tmpl of categoryLabels) {
|
|
1159
|
+
const existing = repoMap.get(tmpl.name.toLowerCase());
|
|
1160
|
+
let status;
|
|
1161
|
+
let repoColor;
|
|
1162
|
+
let repoDesc;
|
|
1163
|
+
if (!existing) {
|
|
1164
|
+
status = "missing";
|
|
1165
|
+
} else {
|
|
1166
|
+
const colorMatch = existing.color.toLowerCase() === tmpl.color.toLowerCase();
|
|
1167
|
+
const descMatch = existing.description.trim() === tmpl.description.trim();
|
|
1168
|
+
if (colorMatch && descMatch) {
|
|
1169
|
+
status = "match";
|
|
1170
|
+
} else if (!colorMatch && !descMatch) {
|
|
1171
|
+
status = "both-mismatch";
|
|
1172
|
+
repoColor = existing.color;
|
|
1173
|
+
repoDesc = existing.description;
|
|
1174
|
+
} else if (!colorMatch) {
|
|
1175
|
+
status = "color-mismatch";
|
|
1176
|
+
repoColor = existing.color;
|
|
1177
|
+
} else {
|
|
1178
|
+
status = "desc-mismatch";
|
|
1179
|
+
repoDesc = existing.description;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
results.push({ template: tmpl, category, status, repoColor, repoDesc });
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
const byCategory = new Map;
|
|
1186
|
+
for (const result of results) {
|
|
1187
|
+
if (!byCategory.has(result.category))
|
|
1188
|
+
byCategory.set(result.category, []);
|
|
1189
|
+
byCategory.get(result.category).push(result);
|
|
1190
|
+
}
|
|
1191
|
+
console.log("");
|
|
1192
|
+
for (const [category, catResults] of byCategory) {
|
|
1193
|
+
const catTitle = category.charAt(0).toUpperCase() + category.slice(1);
|
|
1194
|
+
const catFailing = catResults.filter((r) => isFailing(r.status, strict)).length;
|
|
1195
|
+
const catStatus = catFailing > 0 ? pc7.red(`${catFailing} issue${catFailing !== 1 ? "s" : ""}`) : pc7.green("all good");
|
|
1196
|
+
console.log(`${pc7.bold(catTitle)} ${pc7.dim(`(${catResults.length})`)} — ${catStatus}`);
|
|
1197
|
+
for (const result of catResults) {
|
|
1198
|
+
const icon = statusIcon(result.status);
|
|
1199
|
+
const name = result.template.name.padEnd(28);
|
|
1200
|
+
const color = pc7.dim(`#${result.template.color}`);
|
|
1201
|
+
const lbl = statusLabel(result.status);
|
|
1202
|
+
let line = ` ${icon} ${name} ${color} ${lbl}`;
|
|
1203
|
+
if (result.repoColor) {
|
|
1204
|
+
line += pc7.dim(` (repo: #${result.repoColor})`);
|
|
1205
|
+
}
|
|
1206
|
+
if (result.repoDesc) {
|
|
1207
|
+
line += pc7.dim(`
|
|
1208
|
+
repo desc: "${result.repoDesc}"`);
|
|
1209
|
+
}
|
|
1210
|
+
console.log(line);
|
|
1211
|
+
}
|
|
1212
|
+
console.log("");
|
|
1213
|
+
}
|
|
1214
|
+
const matched = results.filter((r) => r.status === "match").length;
|
|
1215
|
+
const missing = results.filter((r) => r.status === "missing").length;
|
|
1216
|
+
const mismatched = results.filter((r) => r.status !== "match" && r.status !== "missing").length;
|
|
1217
|
+
const total = results.length;
|
|
1218
|
+
const failing = results.filter((r) => isFailing(r.status, strict)).length;
|
|
1219
|
+
const mismatchedStr = mismatched > 0 ? pc7.yellow(`, ${mismatched} mismatched`) : "";
|
|
1220
|
+
const missingStr = missing > 0 ? pc7.red(`, ${missing} missing`) : "";
|
|
1221
|
+
const scoreStr = pc7.bold(`${matched}/${total}`);
|
|
1222
|
+
console.log(`${pc7.bold("Result:")} ${scoreStr} labels matched${mismatchedStr}${missingStr}`);
|
|
1223
|
+
if (failing === 0) {
|
|
1224
|
+
const mismatchHint = !strict && mismatched > 0 ? pc7.dim(` (${mismatched} mismatch${mismatched !== 1 ? "es" : ""} — use --strict to enforce)`) : "";
|
|
1225
|
+
console.log(`${pc7.bold("Compatible:")} ${pc7.green("✔ Yes")}${strict ? " (strict)" : ""}${mismatchHint}`);
|
|
1226
|
+
} else {
|
|
1227
|
+
console.log(`${pc7.bold("Compatible:")} ${pc7.red("✘ No")} — run ${pc7.bold("ghlt apply")} to fix`);
|
|
1228
|
+
process.exit(1);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1066
1233
|
// src/ui/banner.ts
|
|
1067
1234
|
import figlet from "figlet";
|
|
1068
|
-
import
|
|
1235
|
+
import pc8 from "picocolors";
|
|
1069
1236
|
// package.json
|
|
1070
1237
|
var package_default = {
|
|
1071
1238
|
name: "github-labels-template",
|
|
1072
|
-
version: "0.
|
|
1239
|
+
version: "0.8.0",
|
|
1073
1240
|
description: "A CLI tool to apply a curated GitHub labels template to any repository using gh CLI.",
|
|
1074
1241
|
type: "module",
|
|
1075
1242
|
bin: {
|
|
@@ -1134,15 +1301,15 @@ function getAuthor() {
|
|
|
1134
1301
|
return package_default.author ?? "unknown";
|
|
1135
1302
|
}
|
|
1136
1303
|
function showBanner(minimal = false) {
|
|
1137
|
-
console.log(
|
|
1304
|
+
console.log(pc8.cyan(`
|
|
1138
1305
|
` + LOGO));
|
|
1139
|
-
console.log(` ${
|
|
1306
|
+
console.log(` ${pc8.dim("v" + getVersion())} ${pc8.dim("—")} ${pc8.dim("Built by " + getAuthor())}`);
|
|
1140
1307
|
if (!minimal) {
|
|
1141
|
-
console.log(` ${
|
|
1308
|
+
console.log(` ${pc8.dim(package_default.description)}`);
|
|
1142
1309
|
console.log();
|
|
1143
|
-
console.log(` ${
|
|
1144
|
-
console.log(` ${
|
|
1145
|
-
console.log(` ${
|
|
1310
|
+
console.log(` ${pc8.yellow("Star")} ${pc8.cyan("https://gh.waren.build/github-labels-template")}`);
|
|
1311
|
+
console.log(` ${pc8.green("Contribute")} ${pc8.cyan("https://gh.waren.build/github-labels-template/blob/main/CONTRIBUTING.md")}`);
|
|
1312
|
+
console.log(` ${pc8.magenta("Sponsor")} ${pc8.cyan("https://warengonzaga.com/sponsor")}`);
|
|
1146
1313
|
}
|
|
1147
1314
|
console.log();
|
|
1148
1315
|
}
|
|
@@ -1150,7 +1317,7 @@ function showBanner(minimal = false) {
|
|
|
1150
1317
|
// src/index.ts
|
|
1151
1318
|
var isHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
|
1152
1319
|
showBanner(isHelp);
|
|
1153
|
-
var main =
|
|
1320
|
+
var main = defineCommand7({
|
|
1154
1321
|
meta: {
|
|
1155
1322
|
name: "ghlt",
|
|
1156
1323
|
version: getVersion(),
|
|
@@ -1168,7 +1335,8 @@ var main = defineCommand6({
|
|
|
1168
1335
|
wipe: wipe_default,
|
|
1169
1336
|
migrate: migrate_default,
|
|
1170
1337
|
generate: generate_default,
|
|
1171
|
-
list: list_default
|
|
1338
|
+
list: list_default,
|
|
1339
|
+
check: check_default
|
|
1172
1340
|
},
|
|
1173
1341
|
run({ args }) {
|
|
1174
1342
|
if (args.version) {
|