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.
Files changed (2) hide show
  1. package/dist/index.js +187 -19
  2. 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 defineCommand6, runMain } from "citty";
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 pc7 from "picocolors";
1235
+ import pc8 from "picocolors";
1069
1236
  // package.json
1070
1237
  var package_default = {
1071
1238
  name: "github-labels-template",
1072
- version: "0.7.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(pc7.cyan(`
1304
+ console.log(pc8.cyan(`
1138
1305
  ` + LOGO));
1139
- console.log(` ${pc7.dim("v" + getVersion())} ${pc7.dim("—")} ${pc7.dim("Built by " + getAuthor())}`);
1306
+ console.log(` ${pc8.dim("v" + getVersion())} ${pc8.dim("—")} ${pc8.dim("Built by " + getAuthor())}`);
1140
1307
  if (!minimal) {
1141
- console.log(` ${pc7.dim(package_default.description)}`);
1308
+ console.log(` ${pc8.dim(package_default.description)}`);
1142
1309
  console.log();
1143
- console.log(` ${pc7.yellow("Star")} ${pc7.cyan("https://gh.waren.build/github-labels-template")}`);
1144
- console.log(` ${pc7.green("Contribute")} ${pc7.cyan("https://gh.waren.build/github-labels-template/blob/main/CONTRIBUTING.md")}`);
1145
- console.log(` ${pc7.magenta("Sponsor")} ${pc7.cyan("https://warengonzaga.com/sponsor")}`);
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 = defineCommand6({
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-labels-template",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "A CLI tool to apply a curated GitHub labels template to any repository using gh CLI.",
5
5
  "type": "module",
6
6
  "bin": {