tarsk 0.3.38 → 0.3.39

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 CHANGED
@@ -1131,6 +1131,118 @@ class ProjectManagerImpl {
1131
1131
  import { randomUUID as randomUUID2 } from "crypto";
1132
1132
  import { join as join4 } from "path";
1133
1133
 
1134
+ // src/utils/random-words.ts
1135
+ var WORD_LIST = [
1136
+ "panda",
1137
+ "koala",
1138
+ "penguin",
1139
+ "bunny",
1140
+ "kitten",
1141
+ "puppy",
1142
+ "hamster",
1143
+ "otter",
1144
+ "hedgehog",
1145
+ "sloth",
1146
+ "dolphin",
1147
+ "owl",
1148
+ "fox",
1149
+ "deer",
1150
+ "koala",
1151
+ "walrus",
1152
+ "bouncy",
1153
+ "sparkly",
1154
+ "fluffy",
1155
+ "jolly",
1156
+ "zesty",
1157
+ "peppy",
1158
+ "snappy",
1159
+ "perky",
1160
+ "breezy",
1161
+ "chirpy",
1162
+ "dizzy",
1163
+ "fizzy",
1164
+ "glossy",
1165
+ "groggy",
1166
+ "loopy",
1167
+ "quirky",
1168
+ "umbrella",
1169
+ "taco",
1170
+ "pickle",
1171
+ "waffle",
1172
+ "bubble",
1173
+ "puzzle",
1174
+ "rocket",
1175
+ "tornado",
1176
+ "volcano",
1177
+ "rainbow",
1178
+ "diamond",
1179
+ "crystal",
1180
+ "lantern",
1181
+ "treasure",
1182
+ "whisper",
1183
+ "echo",
1184
+ "boing",
1185
+ "splat",
1186
+ "whoosh",
1187
+ "crunch",
1188
+ "splash",
1189
+ "jingle",
1190
+ "wiggle",
1191
+ "giggle",
1192
+ "scramble",
1193
+ "tumble",
1194
+ "fumble",
1195
+ "jumble",
1196
+ "rumble",
1197
+ "mumble",
1198
+ "stumble",
1199
+ "hustle",
1200
+ "castle",
1201
+ "island",
1202
+ "mountain",
1203
+ "forest",
1204
+ "meadow",
1205
+ "lagoon",
1206
+ "canyon",
1207
+ "geyser",
1208
+ "oasis",
1209
+ "glacier",
1210
+ "waterfall",
1211
+ "volcano",
1212
+ "tundra",
1213
+ "savanna",
1214
+ "jungle",
1215
+ "desert",
1216
+ "magic",
1217
+ "dream",
1218
+ "wonder",
1219
+ "spark",
1220
+ "glow",
1221
+ "shimmer",
1222
+ "twinkle",
1223
+ "radiance",
1224
+ "harmony",
1225
+ "melody",
1226
+ "rhythm",
1227
+ "symphony",
1228
+ "adventure",
1229
+ "journey",
1230
+ "quest",
1231
+ "odyssey"
1232
+ ];
1233
+ function getRandomWord() {
1234
+ return WORD_LIST[Math.floor(Math.random() * WORD_LIST.length)];
1235
+ }
1236
+ function generateRandomThreadName() {
1237
+ const word1 = getRandomWord();
1238
+ let word2 = getRandomWord();
1239
+ while (word2 === word1) {
1240
+ word2 = getRandomWord();
1241
+ }
1242
+ return `${word1}-${word2}`;
1243
+ }
1244
+
1245
+ // src/managers/thread-manager.ts
1134
1246
  class ThreadManagerImpl {
1135
1247
  metadataManager;
1136
1248
  gitManager;
@@ -1364,17 +1476,14 @@ class ThreadManagerImpl {
1364
1476
  return join4(projectPath, threadId);
1365
1477
  }
1366
1478
  generateThreadTitle(existingTitles) {
1367
- const threadNumRegex = /^thread-(\d+)$/i;
1368
- let maxNum = 0;
1369
- for (const title of existingTitles) {
1370
- const match = title.match(threadNumRegex);
1371
- if (match) {
1372
- const n = parseInt(match[1], 10);
1373
- if (n > maxNum)
1374
- maxNum = n;
1375
- }
1479
+ let threadTitle = generateRandomThreadName();
1480
+ let attempts = 0;
1481
+ const maxAttempts = 100;
1482
+ while (existingTitles.has(threadTitle) && attempts < maxAttempts) {
1483
+ threadTitle = generateRandomThreadName();
1484
+ attempts++;
1376
1485
  }
1377
- return `Thread ${maxNum + 1}`;
1486
+ return threadTitle;
1378
1487
  }
1379
1488
  async listFiles(threadId) {
1380
1489
  const thread = await this.getThread(threadId);
@@ -7104,6 +7213,15 @@ new file mode 100644
7104
7213
  } catch {
7105
7214
  return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
7106
7215
  }
7216
+ const currentBranch = await new Promise((resolve4) => {
7217
+ const proc = spawn8("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
7218
+ let out = "";
7219
+ proc.stdout.on("data", (d) => {
7220
+ out += d.toString();
7221
+ });
7222
+ proc.on("close", () => resolve4(out.trim()));
7223
+ proc.on("error", () => resolve4(""));
7224
+ });
7107
7225
  const diff = await new Promise((resolveDiff, reject) => {
7108
7226
  const runPlainDiff = () => {
7109
7227
  const proc2 = spawn8("git", ["diff"], { cwd: gitRoot });
@@ -7136,19 +7254,97 @@ new file mode 100644
7136
7254
  });
7137
7255
  proc.on("error", reject);
7138
7256
  });
7139
- if (!diff.trim()) {
7140
- return c.json({ error: "No changes to generate PR info for. Make sure you have uncommitted changes." }, 400);
7257
+ const baseBranch = await new Promise((resolve4) => {
7258
+ const proc = spawn8("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], { cwd: gitRoot });
7259
+ let out = "";
7260
+ proc.stdout.on("data", (d) => {
7261
+ out += d.toString();
7262
+ });
7263
+ proc.on("close", (code) => {
7264
+ if (code === 0 && out.trim()) {
7265
+ const match = out.trim().match(/refs\/remotes\/origin\/(.+)/);
7266
+ resolve4(match ? match[1] : null);
7267
+ } else {
7268
+ resolve4(null);
7269
+ }
7270
+ });
7271
+ proc.on("error", () => resolve4(null));
7272
+ });
7273
+ const effectiveBaseBranch = baseBranch || await (async () => {
7274
+ const candidates = ["main", "master", "develop", "staging"];
7275
+ for (const branch of candidates) {
7276
+ const exists = await new Promise((resolve4) => {
7277
+ const proc = spawn8("git", ["rev-parse", "--verify", `origin/${branch}`], { cwd: gitRoot });
7278
+ proc.on("close", (code) => resolve4(code === 0));
7279
+ proc.on("error", () => resolve4(false));
7280
+ });
7281
+ if (exists)
7282
+ return branch;
7283
+ }
7284
+ return null;
7285
+ })();
7286
+ let commitLog = "";
7287
+ if (effectiveBaseBranch && currentBranch !== effectiveBaseBranch) {
7288
+ commitLog = await new Promise((resolve4) => {
7289
+ const proc = spawn8("git", ["log", "--oneline", `${effectiveBaseBranch}..${currentBranch}`], { cwd: gitRoot });
7290
+ let out = "";
7291
+ proc.stdout.on("data", (d) => {
7292
+ out += d.toString();
7293
+ });
7294
+ proc.on("close", () => resolve4(out.trim()));
7295
+ proc.on("error", () => resolve4(""));
7296
+ });
7141
7297
  }
7142
- const truncatedDiff = diff.length > 3000 ? diff.substring(0, 3000) + `
7298
+ let detailedCommits = "";
7299
+ if (effectiveBaseBranch && currentBranch !== effectiveBaseBranch && commitLog) {
7300
+ detailedCommits = await new Promise((resolve4) => {
7301
+ const proc = spawn8("git", ["log", `${effectiveBaseBranch}..${currentBranch}`, "--format=%h %s%n%b%n---"], { cwd: gitRoot });
7302
+ let out = "";
7303
+ proc.stdout.on("data", (d) => {
7304
+ out += d.toString();
7305
+ });
7306
+ proc.on("close", () => {
7307
+ resolve4(out.length > 2000 ? out.substring(0, 2000) + `
7308
+ ...(more commits)` : out);
7309
+ });
7310
+ proc.on("error", () => resolve4(""));
7311
+ });
7312
+ }
7313
+ if (!diff.trim() && !commitLog.trim()) {
7314
+ return c.json({
7315
+ error: effectiveBaseBranch ? `No changes to generate PR info for. The current branch "${currentBranch}" has no uncommitted changes and no commits ahead of "${effectiveBaseBranch}".` : "No changes to generate PR info for. Make sure you have uncommitted changes or commits ahead of the base branch."
7316
+ }, 400);
7317
+ }
7318
+ let promptContent;
7319
+ if (commitLog.trim()) {
7320
+ promptContent = `Based on the following commits in branch "${currentBranch}" compared to "${effectiveBaseBranch}", generate a pull request title and description.
7321
+
7322
+ Commit log:
7323
+ ${commitLog}
7324
+
7325
+ Detailed commit messages:
7326
+ ${detailedCommits || "(no details available)"}
7327
+
7328
+ ${diff.trim() ? `
7329
+ Additionally, there are uncommitted changes:
7330
+ ${diff.length > 1500 ? diff.substring(0, 1500) + `
7331
+ ...(truncated)` : diff}` : ""}`;
7332
+ } else {
7333
+ const truncatedDiff = diff.length > 3000 ? diff.substring(0, 3000) + `
7143
7334
  ...(truncated)` : diff;
7144
- const prompt = `Based on the following git diff, generate a pull request title and description. Follow these guidelines:
7335
+ promptContent = `Based on the following git diff, generate a pull request title and description.
7336
+
7337
+ Git diff:
7338
+ ${truncatedDiff}`;
7339
+ }
7340
+ const prompt = `${promptContent}
7341
+
7342
+ Follow these guidelines:
7145
7343
  - Title: Concise, descriptive, under 72 characters
7146
7344
  - Description: Clear explanation of changes, why they were made, and any relevant context
7147
- - Use markdown formatting for the description
7345
+ - Use markdown formatting for the description (bullet points, headers, etc.)
7148
7346
  - Be professional and clear
7149
-
7150
- Git diff:
7151
- ${truncatedDiff}
7347
+ - If multiple changes, group them logically in the description
7152
7348
 
7153
7349
  Generate the response in this exact format:
7154
7350
  TITLE: <title here>
@@ -7173,7 +7369,7 @@ DESCRIPTION: <description here>`;
7173
7369
  const descriptionMatch = prInfo.match(/DESCRIPTION:\s*(.+?)$/s);
7174
7370
  const title = titleMatch ? titleMatch[1].trim() : "Update";
7175
7371
  const description = descriptionMatch ? descriptionMatch[1].trim() : "";
7176
- return c.json({ title, description });
7372
+ return c.json({ title, description, baseBranch: effectiveBaseBranch, currentBranch });
7177
7373
  } catch (error) {
7178
7374
  const message = error instanceof Error ? error.message : "Failed to generate PR info";
7179
7375
  return c.json({ error: message }, 500);
@@ -7290,6 +7486,148 @@ DESCRIPTION: <description here>`;
7290
7486
  return c.json({ error: message }, 500);
7291
7487
  }
7292
7488
  });
7489
+ router.get("/github-status/:threadId", async (c) => {
7490
+ try {
7491
+ const threadId = c.req.param("threadId");
7492
+ const thread = await metadataManager.loadThreads().then((threads) => threads.find((t) => t.id === threadId));
7493
+ if (!thread) {
7494
+ return c.json({ error: "Thread not found" }, 404);
7495
+ }
7496
+ const repoPath = thread.path;
7497
+ if (!repoPath) {
7498
+ return c.json({ error: "Thread path not found" }, 404);
7499
+ }
7500
+ const absolutePath = resolveThreadPath(repoPath);
7501
+ let gitRoot;
7502
+ try {
7503
+ gitRoot = await getGitRoot(absolutePath);
7504
+ } catch {
7505
+ return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
7506
+ }
7507
+ const remoteUrl = await new Promise((resolve4) => {
7508
+ const proc = spawn8("git", ["remote", "get-url", "origin"], { cwd: gitRoot });
7509
+ let out = "";
7510
+ proc.stdout.on("data", (d) => {
7511
+ out += d.toString();
7512
+ });
7513
+ proc.on("close", (code) => {
7514
+ if (code === 0) {
7515
+ resolve4(out.trim());
7516
+ } else {
7517
+ resolve4("");
7518
+ }
7519
+ });
7520
+ proc.on("error", () => resolve4(""));
7521
+ });
7522
+ const isOnGitHub = remoteUrl.includes("github.com");
7523
+ let repoUrl = "";
7524
+ if (isOnGitHub) {
7525
+ if (remoteUrl.startsWith("https://")) {
7526
+ repoUrl = remoteUrl.replace(/\.git$/, "");
7527
+ } else if (remoteUrl.startsWith("git@")) {
7528
+ const match = remoteUrl.match(/git@github\.com:(.+?)\.git$/);
7529
+ if (match) {
7530
+ repoUrl = `https://github.com/${match[1]}`;
7531
+ }
7532
+ }
7533
+ }
7534
+ return c.json({
7535
+ isOnGitHub,
7536
+ remoteUrl,
7537
+ canCreateRepo: !isOnGitHub && remoteUrl.length === 0,
7538
+ repoUrl
7539
+ });
7540
+ } catch (error) {
7541
+ const message = error instanceof Error ? error.message : "Failed to check GitHub status";
7542
+ return c.json({ error: message }, 500);
7543
+ }
7544
+ });
7545
+ router.post("/create-repo/:threadId", async (c) => {
7546
+ try {
7547
+ const threadId = c.req.param("threadId");
7548
+ const body = await c.req.json().catch(() => ({}));
7549
+ const { repoName, description, isPrivate } = body;
7550
+ const thread = await metadataManager.loadThreads().then((threads) => threads.find((t) => t.id === threadId));
7551
+ if (!thread) {
7552
+ return c.json({ error: "Thread not found" }, 404);
7553
+ }
7554
+ const repoPath = thread.path;
7555
+ if (!repoPath) {
7556
+ return c.json({ error: "Thread path not found" }, 404);
7557
+ }
7558
+ const absolutePath = resolveThreadPath(repoPath);
7559
+ let gitRoot;
7560
+ try {
7561
+ gitRoot = await getGitRoot(absolutePath);
7562
+ } catch {
7563
+ return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
7564
+ }
7565
+ const remoteUrl = await new Promise((resolve4) => {
7566
+ const proc = spawn8("git", ["remote", "get-url", "origin"], { cwd: gitRoot });
7567
+ let out = "";
7568
+ proc.stdout.on("data", (d) => {
7569
+ out += d.toString();
7570
+ });
7571
+ proc.on("close", (code) => {
7572
+ if (code === 0) {
7573
+ resolve4(out.trim());
7574
+ } else {
7575
+ resolve4("");
7576
+ }
7577
+ });
7578
+ proc.on("error", () => resolve4(""));
7579
+ });
7580
+ if (remoteUrl) {
7581
+ return c.json({ error: "Repository already has a remote origin" }, 400);
7582
+ }
7583
+ const _currentBranch = await new Promise((resolve4, reject) => {
7584
+ const proc = spawn8("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
7585
+ let out = "";
7586
+ proc.stdout.on("data", (d) => {
7587
+ out += d.toString();
7588
+ });
7589
+ proc.on("close", () => resolve4(out.trim()));
7590
+ proc.on("error", reject);
7591
+ });
7592
+ const repoUrl = await new Promise((resolve4, reject) => {
7593
+ const args = ["repo", "create"];
7594
+ if (repoName) {
7595
+ args.push(repoName);
7596
+ }
7597
+ if (description) {
7598
+ args.push("--description", description);
7599
+ }
7600
+ if (isPrivate) {
7601
+ args.push("--private");
7602
+ } else {
7603
+ args.push("--public");
7604
+ }
7605
+ args.push("--source", ".", "--push");
7606
+ const proc = spawn8("gh", args, { cwd: gitRoot });
7607
+ let out = "";
7608
+ let err = "";
7609
+ proc.stdout.on("data", (d) => {
7610
+ out += d.toString();
7611
+ });
7612
+ proc.stderr.on("data", (d) => {
7613
+ err += d.toString();
7614
+ });
7615
+ proc.on("close", (code) => {
7616
+ if (code === 0) {
7617
+ const urlMatch = out.match(/https:\/\/[^\s]+/);
7618
+ resolve4(urlMatch ? urlMatch[0] : out.trim());
7619
+ } else {
7620
+ reject(new Error(err || "Failed to create repository"));
7621
+ }
7622
+ });
7623
+ proc.on("error", () => reject(new Error("GitHub CLI (gh) not found. Please install it to create repositories.")));
7624
+ });
7625
+ return c.json({ success: true, repoUrl });
7626
+ } catch (error) {
7627
+ const message = error instanceof Error ? error.message : "Failed to create repository";
7628
+ return c.json({ error: message }, 500);
7629
+ }
7630
+ });
7293
7631
  return router;
7294
7632
  }
7295
7633