techunter 0.1.11 → 1.0.1

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 (4) hide show
  1. package/README.md +135 -107
  2. package/dist/index.js +1960 -1003
  3. package/dist/mcp.js +1212 -382
  4. package/package.json +1 -1
package/dist/mcp.js CHANGED
@@ -39,12 +39,22 @@ __export(github_exports, {
39
39
  createTask: () => createTask,
40
40
  editTask: () => editTask,
41
41
  embedBaseCommit: () => embedBaseCommit,
42
+ embedTargetBranch: () => embedTargetBranch,
42
43
  ensureLabels: () => ensureLabels,
44
+ ensureRemoteBranch: () => ensureRemoteBranch,
43
45
  extractBaseCommit: () => extractBaseCommit,
46
+ extractTargetBranch: () => extractTargetBranch,
44
47
  formatGuideAsMarkdown: () => formatGuideAsMarkdown,
45
48
  getAuthenticatedUser: () => getAuthenticatedUser,
49
+ getBranchHeadSha: () => getBranchHeadSha,
46
50
  getDefaultBranch: () => getDefaultBranch,
51
+ getIssueNumberFromBranch: () => getIssueNumberFromBranch,
52
+ getOpenSubtasks: () => getOpenSubtasks,
53
+ getRepoFile: () => getRepoFile,
47
54
  getTask: () => getTask,
55
+ getTaskBranch: () => getTaskBranch,
56
+ getTaskPR: () => getTaskPR,
57
+ getTaskPRDiff: () => getTaskPRDiff,
48
58
  isCollaborator: () => isCollaborator,
49
59
  listComments: () => listComments,
50
60
  listMyTasks: () => listMyTasks,
@@ -52,9 +62,11 @@ __export(github_exports, {
52
62
  listTasksForReview: () => listTasksForReview,
53
63
  markInReview: () => markInReview,
54
64
  mergeWorkerIntoBase: () => mergeWorkerIntoBase,
65
+ moveTask: () => moveTask,
55
66
  postComment: () => postComment,
56
67
  postGuideComment: () => postGuideComment,
57
- rejectTask: () => rejectTask
68
+ rejectTask: () => rejectTask,
69
+ upsertRepoFile: () => upsertRepoFile
58
70
  });
59
71
  import { Octokit } from "@octokit/rest";
60
72
  import { fetch as undiciFetch } from "undici";
@@ -117,11 +129,22 @@ function extractBaseCommit(body) {
117
129
  const match = body.match(/<!-- techunter-base:([a-f0-9]{7,40}) -->/);
118
130
  return match?.[1] ?? null;
119
131
  }
120
- async function createTask(config, title, body, baseCommit) {
132
+ function embedTargetBranch(body, branch) {
133
+ return `${body}
134
+ ${TARGET_BRANCH_MARKER}${branch} -->`;
135
+ }
136
+ function extractTargetBranch(body) {
137
+ if (!body) return null;
138
+ const match = body.match(/<!-- techunter-target:([^\s>]+) -->/);
139
+ return match?.[1] ?? null;
140
+ }
141
+ async function createTask(config, title, body, baseCommit, targetBranch) {
121
142
  const octokit = createOctokit(config.githubToken);
122
143
  const { owner, repo } = config.github;
123
144
  await ensureLabels(config);
124
- const finalBody = baseCommit ? embedBaseCommit(body ?? "", baseCommit) : body;
145
+ let finalBody = body ?? "";
146
+ if (baseCommit) finalBody = embedBaseCommit(finalBody, baseCommit);
147
+ if (targetBranch) finalBody = embedTargetBranch(finalBody, targetBranch);
125
148
  const { data } = await octokit.issues.create({
126
149
  owner,
127
150
  repo,
@@ -134,13 +157,22 @@ async function createTask(config, title, body, baseCommit) {
134
157
  async function mergeWorkerIntoBase(config, workerBranch, baseBranch) {
135
158
  const octokit = createOctokit(config.githubToken);
136
159
  const { owner, repo } = config.github;
137
- await octokit.repos.merge({
138
- owner,
139
- repo,
140
- base: baseBranch,
141
- head: workerBranch,
142
- commit_message: `chore: merge ${workerBranch} into ${baseBranch}`
143
- });
160
+ try {
161
+ await octokit.repos.merge({
162
+ owner,
163
+ repo,
164
+ base: baseBranch,
165
+ head: workerBranch,
166
+ commit_message: `chore: merge ${workerBranch} into ${baseBranch}`
167
+ });
168
+ } catch (err) {
169
+ if (err.status === 409) {
170
+ throw new Error(
171
+ `Merge conflict: ${workerBranch} cannot be merged into ${baseBranch} cleanly. Resolve conflicts manually.`
172
+ );
173
+ }
174
+ throw err;
175
+ }
144
176
  }
145
177
  async function claimTask(config, number, username) {
146
178
  const octokit = createOctokit(config.githubToken);
@@ -216,6 +248,23 @@ async function postGuideComment(config, number, guide) {
216
248
  body
217
249
  });
218
250
  }
251
+ async function ensureRemoteBranch(config, branchName, fallbackBase) {
252
+ const octokit = createOctokit(config.githubToken);
253
+ const { owner, repo } = config.github;
254
+ try {
255
+ await octokit.repos.getBranch({ owner, repo, branch: branchName });
256
+ return;
257
+ } catch (err) {
258
+ if (err.status !== 404) throw err;
259
+ }
260
+ const { data: baseRef } = await octokit.repos.getBranch({ owner, repo, branch: fallbackBase });
261
+ await octokit.git.createRef({
262
+ owner,
263
+ repo,
264
+ ref: `refs/heads/${branchName}`,
265
+ sha: baseRef.commit.sha
266
+ });
267
+ }
219
268
  async function createPR(config, title, body, branch, base) {
220
269
  const octokit = createOctokit(config.githubToken);
221
270
  const { owner, repo } = config.github;
@@ -232,14 +281,11 @@ async function createPR(config, title, body, branch, base) {
232
281
  async function markInReview(config, number) {
233
282
  const octokit = createOctokit(config.githubToken);
234
283
  const { owner, repo } = config.github;
235
- try {
236
- await octokit.issues.removeLabel({
237
- owner,
238
- repo,
239
- issue_number: number,
240
- name: LABEL_CLAIMED
241
- });
242
- } catch {
284
+ for (const label of [LABEL_CLAIMED, LABEL_CHANGES_NEEDED]) {
285
+ try {
286
+ await octokit.issues.removeLabel({ owner, repo, issue_number: number, name: label });
287
+ } catch {
288
+ }
243
289
  }
244
290
  await octokit.issues.addLabels({
245
291
  owner,
@@ -354,28 +400,146 @@ async function editTask(config, number, title, body) {
354
400
  const { owner, repo } = config.github;
355
401
  await octokit.issues.update({ owner, repo, issue_number: number, title, body });
356
402
  }
403
+ async function upsertRepoFile(config, filePath, content, message) {
404
+ const octokit = createOctokit(config.githubToken);
405
+ const { owner, repo } = config.github;
406
+ let sha;
407
+ try {
408
+ const { data: data2 } = await octokit.repos.getContent({ owner, repo, path: filePath });
409
+ if (!Array.isArray(data2) && data2.type === "file") {
410
+ sha = data2.sha;
411
+ }
412
+ } catch {
413
+ }
414
+ const { data } = await octokit.repos.createOrUpdateFileContents({
415
+ owner,
416
+ repo,
417
+ path: filePath,
418
+ message,
419
+ content: Buffer.from(content, "utf-8").toString("base64"),
420
+ ...sha ? { sha } : {}
421
+ });
422
+ return data.content?.html_url ?? `https://github.com/${owner}/${repo}/blob/main/${filePath}`;
423
+ }
424
+ async function getRepoFile(config, filePath) {
425
+ const octokit = createOctokit(config.githubToken);
426
+ const { owner, repo } = config.github;
427
+ try {
428
+ const { data } = await octokit.repos.getContent({ owner, repo, path: filePath });
429
+ if (!Array.isArray(data) && data.type === "file" && "content" in data) {
430
+ return Buffer.from(data.content, "base64").toString("utf-8");
431
+ }
432
+ return null;
433
+ } catch {
434
+ return null;
435
+ }
436
+ }
357
437
  async function getDefaultBranch(config) {
358
438
  const octokit = createOctokit(config.githubToken);
359
439
  const { owner, repo } = config.github;
360
440
  const { data } = await octokit.repos.get({ owner, repo });
361
441
  return data.default_branch;
362
442
  }
363
- async function acceptTask(config, issueNumber, headBranch) {
443
+ async function getTaskBranch(config, issueNumber) {
364
444
  const octokit = createOctokit(config.githubToken);
365
445
  const { owner, repo } = config.github;
366
446
  const { data: prs } = await octokit.pulls.list({ owner, repo, state: "open", per_page: 100 });
367
- const pr = headBranch ? prs.find((p) => p.head.ref === headBranch) : prs.find((p) => p.head.ref.startsWith(`task-${issueNumber}-`) || p.head.ref.startsWith("worker-"));
368
- if (!pr) throw new Error(`No open PR found for task #${issueNumber}`);
369
- const { data: merge } = await octokit.pulls.merge({
447
+ const pr = prs.find((p) => new RegExp(`Closes #${issueNumber}\\b`, "i").test(p.body ?? ""));
448
+ if (pr) return pr.head.ref;
449
+ const { data: branches } = await octokit.repos.listBranches({ owner, repo, per_page: 100 });
450
+ const taskBranch = branches.find((b) => new RegExp(`^task-${issueNumber}-`).test(b.name));
451
+ return taskBranch?.name ?? null;
452
+ }
453
+ async function getBranchHeadSha(config, branchName) {
454
+ const octokit = createOctokit(config.githubToken);
455
+ const { owner, repo } = config.github;
456
+ try {
457
+ const { data } = await octokit.repos.getBranch({ owner, repo, branch: branchName });
458
+ return data.commit.sha;
459
+ } catch {
460
+ return null;
461
+ }
462
+ }
463
+ async function moveTask(config, issueNumber, newTargetBranch, newBaseCommit) {
464
+ const octokit = createOctokit(config.githubToken);
465
+ const { owner, repo } = config.github;
466
+ const { data } = await octokit.issues.get({ owner, repo, issue_number: issueNumber });
467
+ let body = data.body ?? "";
468
+ body = body.replace(/\n*<!-- techunter-base:[a-f0-9]{7,40} -->/g, "");
469
+ body = body.replace(/\n*<!-- techunter-target:[^\s>]+ -->/g, "");
470
+ body = embedBaseCommit(body, newBaseCommit);
471
+ body = embedTargetBranch(body, newTargetBranch);
472
+ await octokit.issues.update({ owner, repo, issue_number: issueNumber, body });
473
+ }
474
+ async function getTaskPR(config, issueNumber) {
475
+ const octokit = createOctokit(config.githubToken);
476
+ const { owner, repo } = config.github;
477
+ const { data: prs } = await octokit.pulls.list({ owner, repo, state: "open", per_page: 100 });
478
+ const pr = prs.find(
479
+ (p) => new RegExp(`Closes #${issueNumber}\\b`, "i").test(p.body ?? "")
480
+ );
481
+ if (!pr) return null;
482
+ return { number: pr.number, url: pr.html_url, body: pr.body ?? "", baseBranch: pr.base.ref };
483
+ }
484
+ async function getOpenSubtasks(config, targetBranch) {
485
+ const octokit = createOctokit(config.githubToken);
486
+ const { owner, repo } = config.github;
487
+ const { data } = await octokit.issues.listForRepo({
488
+ owner,
489
+ repo,
490
+ state: "open",
491
+ per_page: 100
492
+ });
493
+ return data.filter((issue) => !issue.pull_request).filter((issue) => extractTargetBranch(issue.body ?? null) === targetBranch).map((issue) => issue.number);
494
+ }
495
+ async function getIssueNumberFromBranch(config, branch) {
496
+ const octokit = createOctokit(config.githubToken);
497
+ const { owner, repo } = config.github;
498
+ const { data: prs } = await octokit.pulls.list({ owner, repo, state: "open", per_page: 100 });
499
+ const pr = prs.find((p) => p.head.ref === branch);
500
+ if (!pr) return null;
501
+ const match = (pr.body ?? "").match(/Closes #(\d+)/i);
502
+ if (!match) return null;
503
+ return { issueNumber: parseInt(match[1], 10), prUrl: pr.html_url };
504
+ }
505
+ async function getTaskPRDiff(config, prNumber) {
506
+ const octokit = createOctokit(config.githubToken);
507
+ const { owner, repo } = config.github;
508
+ const response = await octokit.pulls.get({
370
509
  owner,
371
510
  repo,
372
- pull_number: pr.number,
373
- merge_method: "merge"
511
+ pull_number: prNumber,
512
+ mediaType: { format: "diff" }
374
513
  });
375
- await closeTask(config, issueNumber);
376
- return { prNumber: pr.number, prUrl: pr.html_url, sha: merge.sha ?? "" };
514
+ return response.data;
515
+ }
516
+ async function acceptTask(config, issueNumber) {
517
+ const octokit = createOctokit(config.githubToken);
518
+ const { owner, repo } = config.github;
519
+ const { data: prs } = await octokit.pulls.list({ owner, repo, state: "open", per_page: 100 });
520
+ const pr = prs.find(
521
+ (p) => new RegExp(`Closes #${issueNumber}\\b`, "i").test(p.body ?? "")
522
+ );
523
+ if (!pr) throw new Error(`No open PR found for task #${issueNumber}`);
524
+ try {
525
+ const { data: merge } = await octokit.pulls.merge({
526
+ owner,
527
+ repo,
528
+ pull_number: pr.number,
529
+ merge_method: "merge"
530
+ });
531
+ await closeTask(config, issueNumber);
532
+ return { prNumber: pr.number, prUrl: pr.html_url, sha: merge.sha ?? "", baseBranch: pr.base.ref };
533
+ } catch (err) {
534
+ if (err.status === 405) {
535
+ throw new Error(
536
+ `PR #${pr.number} cannot be merged \u2014 may have conflicts or is not in a mergeable state.`
537
+ );
538
+ }
539
+ throw err;
540
+ }
377
541
  }
378
- var LABEL_AVAILABLE, LABEL_CLAIMED, LABEL_IN_REVIEW, LABEL_CHANGES_NEEDED, LABELS, TECHUNTER_LABELS, BASE_COMMIT_MARKER;
542
+ var LABEL_AVAILABLE, LABEL_CLAIMED, LABEL_IN_REVIEW, LABEL_CHANGES_NEEDED, LABELS, TECHUNTER_LABELS, BASE_COMMIT_MARKER, TARGET_BRANCH_MARKER;
379
543
  var init_github = __esm({
380
544
  "src/lib/github.ts"() {
381
545
  "use strict";
@@ -392,6 +556,7 @@ var init_github = __esm({
392
556
  ];
393
557
  TECHUNTER_LABELS = /* @__PURE__ */ new Set([LABEL_AVAILABLE, LABEL_CLAIMED, LABEL_IN_REVIEW, LABEL_CHANGES_NEEDED]);
394
558
  BASE_COMMIT_MARKER = "<!-- techunter-base:";
559
+ TARGET_BRANCH_MARKER = "<!-- techunter-target:";
395
560
  }
396
561
  });
397
562
 
@@ -428,6 +593,17 @@ function makeWorkerBranchName(username) {
428
593
  const slug = username.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "user";
429
594
  return `worker-${slug}`;
430
595
  }
596
+ function makeTaskBranchName(issueNumber, username) {
597
+ const slug = username.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "user";
598
+ return `task-${issueNumber}-${slug}`;
599
+ }
600
+ function isTaskBranch(branch) {
601
+ return /^task-\d+-/.test(branch);
602
+ }
603
+ function parseIssueNumberFromBranch(branch) {
604
+ const match = branch.match(/^task-(\d+)-/);
605
+ return match ? parseInt(match[1], 10) : null;
606
+ }
431
607
  async function getCurrentCommit() {
432
608
  return (await git.revparse(["HEAD"])).trim();
433
609
  }
@@ -548,16 +724,27 @@ async function getRemoteHeadSha(baseBranch) {
548
724
  await git.fetch("origin", baseBranch);
549
725
  return (await git.revparse([`origin/${baseBranch}`])).trim();
550
726
  }
551
- async function resetOrCreateBranch(branchName, sha) {
552
- const branches = await git.branch();
553
- const localExists = Object.keys(branches.branches).some((b) => b === branchName);
554
- if (localExists) {
727
+ async function checkoutFromCommit(branchName, sha) {
728
+ const branches = await git.branch(["-a"]);
729
+ const exists = Object.keys(branches.branches).some(
730
+ (b) => b === branchName || b === `remotes/origin/${branchName}`
731
+ );
732
+ if (exists) {
555
733
  await git.checkout(branchName);
556
- await git.reset(["--hard", sha]);
557
734
  } else {
558
735
  await git.checkoutBranch(branchName, sha);
559
736
  }
560
737
  }
738
+ async function hasUncommittedChanges() {
739
+ const status = await git.status();
740
+ return !status.isClean();
741
+ }
742
+ async function stash(message) {
743
+ await git.stash(["push", "-u", "-m", message]);
744
+ }
745
+ async function stashPop() {
746
+ await git.stash(["pop"]);
747
+ }
561
748
 
562
749
  // src/tools/pick/index.ts
563
750
  init_github();
@@ -578,7 +765,8 @@ var configSchema = z.object({
578
765
  }),
579
766
  taskState: z.object({
580
767
  activeIssueNumber: z.number().optional(),
581
- baseCommit: z.string().optional()
768
+ baseCommit: z.string().optional(),
769
+ activeBranch: z.string().optional()
582
770
  }).optional()
583
771
  });
584
772
  var store = new Conf({
@@ -665,11 +853,22 @@ function colorStatus(status) {
665
853
  return padded;
666
854
  }
667
855
  }
856
+ function parentIssueFromBranch(branch) {
857
+ if (!isTaskBranch(branch)) return null;
858
+ const match = branch.match(/^task-(\d+)-/);
859
+ return match ? parseInt(match[1], 10) : null;
860
+ }
861
+ function getParentIssueNumber(issue) {
862
+ const target = extractTargetBranch(issue.body);
863
+ if (!target) return null;
864
+ return parentIssueFromBranch(target);
865
+ }
668
866
  function printTaskDetail(issue) {
669
867
  const divider = chalk2.dim("\u2500".repeat(70));
868
+ const parentNum = getParentIssueNumber(issue);
670
869
  console.log("\n" + divider);
671
870
  console.log(
672
- chalk2.bold(` #${issue.number}`) + " " + colorStatus(getStatus(issue)) + " " + chalk2.dim(issue.assignee ? `@${issue.assignee}` : "\u2014")
871
+ chalk2.bold(` #${issue.number}`) + " " + colorStatus(getStatus(issue)) + " " + chalk2.dim(issue.assignee ? `@${issue.assignee}` : "\u2014") + (parentNum ? chalk2.dim(` sub-task of #${parentNum}`) : "")
673
872
  );
674
873
  console.log(chalk2.bold("\n " + issue.title));
675
874
  if (issue.body) {
@@ -689,12 +888,34 @@ async function printTaskList(config) {
689
888
  if (tasks.length === 0) {
690
889
  console.log(chalk2.dim(" (no tasks)"));
691
890
  } else {
692
- for (const t of tasks) {
891
+ let printTask2 = function(t, indent, connector, isLast) {
693
892
  const num = `#${t.number}`.padEnd(5);
694
893
  const status = colorStatus(getStatus(t));
695
894
  const assignee = (t.assignee ? `@${t.assignee}` : "\u2014").padEnd(16);
696
- const title = t.title.length > 36 ? t.title.slice(0, 33) + "..." : t.title;
697
- console.log(` ${num}${status}${assignee}${title}`);
895
+ const fullPrefix = indent + connector;
896
+ const maxTitle = 36 - fullPrefix.length;
897
+ const title = t.title.length > maxTitle ? t.title.slice(0, maxTitle - 3) + "..." : t.title;
898
+ console.log(` ${num}${status}${assignee}${chalk2.dim(fullPrefix)}${title}`);
899
+ const children = childrenOf.get(t.number) ?? [];
900
+ const childIndent = indent + (isLast ? " " : "\u2502 ");
901
+ for (let i = 0; i < children.length; i++) {
902
+ const childIsLast = i === children.length - 1;
903
+ printTask2(children[i], childIndent, childIsLast ? "\u2514\u2500 " : "\u251C\u2500 ", childIsLast);
904
+ }
905
+ };
906
+ var printTask = printTask2;
907
+ const taskMap = new Map(tasks.map((t) => [t.number, t]));
908
+ const childrenOf = /* @__PURE__ */ new Map();
909
+ for (const t of tasks) {
910
+ const parentNum = getParentIssueNumber(t);
911
+ const key = parentNum !== null && taskMap.has(parentNum) ? parentNum : null;
912
+ if (!childrenOf.has(key)) childrenOf.set(key, []);
913
+ childrenOf.get(key).push(t);
914
+ }
915
+ const roots = childrenOf.get(null) ?? [];
916
+ for (let i = 0; i < roots.length; i++) {
917
+ const isLast = i === roots.length - 1;
918
+ printTask2(roots[i], "", isLast ? "\u2514\u2500 " : "\u251C\u2500 ", isLast);
698
919
  }
699
920
  }
700
921
  console.log(divider);
@@ -873,9 +1094,19 @@ var definition = {
873
1094
  };
874
1095
  async function run(_input, config) {
875
1096
  const taskState = getConfig().taskState;
876
- const issueNumber = taskState?.activeIssueNumber;
1097
+ const currentBranch = await getCurrentBranch();
1098
+ let issueNumber = taskState?.activeIssueNumber && taskState?.activeBranch && currentBranch === taskState.activeBranch ? taskState.activeIssueNumber : void 0;
877
1099
  if (!issueNumber) {
878
- return "No active task found. Claim a task first with /pick.";
1100
+ const fromBranch = parseIssueNumberFromBranch(currentBranch);
1101
+ if (fromBranch) {
1102
+ issueNumber = fromBranch;
1103
+ } else {
1104
+ const found = await getIssueNumberFromBranch(config, currentBranch);
1105
+ if (!found) {
1106
+ return "No active task found. Claim a task first with /pick.";
1107
+ }
1108
+ issueNumber = found.issueNumber;
1109
+ }
879
1110
  }
880
1111
  let spinner = ora("Loading task and diff\u2026").start();
881
1112
  const diffPromise = taskState?.baseCommit ? getDiffFromCommit(taskState.baseCommit) : getDiff();
@@ -885,9 +1116,16 @@ async function run(_input, config) {
885
1116
  getAuthenticatedUser(config)
886
1117
  ]);
887
1118
  spinner.stop();
888
- const workerBranch = makeWorkerBranchName(issue.author ?? me);
1119
+ const targetBranch = extractTargetBranch(issue.body) ?? makeWorkerBranchName(issue.author ?? me);
889
1120
  const branch = await getCurrentBranch();
890
1121
  const isSelfSubmit = issue.author !== null && issue.author === me;
1122
+ spinner = ora("Checking for open sub-tasks\u2026").start();
1123
+ const openSubtaskNumbers = await getOpenSubtasks(config, branch);
1124
+ spinner.stop();
1125
+ if (openSubtaskNumbers.length > 0) {
1126
+ return `Cannot submit: ${openSubtaskNumbers.length} sub-task(s) still open:
1127
+ ` + openSubtaskNumbers.map((n) => ` - #${n}`).join("\n") + "\nComplete all sub-tasks before submitting.";
1128
+ }
891
1129
  let review = "";
892
1130
  if (!isSelfSubmit) {
893
1131
  const reviewSpinner = ora("Reviewing changes\u2026").start();
@@ -948,26 +1186,35 @@ async function run(_input, config) {
948
1186
  spinner.stop();
949
1187
  console.error(chalk5.yellow(`Warning: failed to close issue: ${err.message}`));
950
1188
  }
951
- setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0 } });
1189
+ setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0, activeBranch: void 0 } });
952
1190
  return `Task #${issueNumber} committed and closed.
953
1191
  Commit: "${commitMessage.trim()}"`;
954
1192
  }
955
- spinner = ora("Creating pull request\u2026").start();
1193
+ spinner = ora("Checking for existing PR\u2026").start();
1194
+ const existingPR = await getTaskPR(config, issueNumber);
1195
+ spinner.stop();
956
1196
  let prUrl;
957
- try {
958
- const prBody = [
959
- `Closes #${issueNumber}`,
960
- issue.body ? `
1197
+ if (existingPR) {
1198
+ prUrl = existingPR.url;
1199
+ console.log(chalk5.dim(` Existing PR found: ${prUrl} \u2014 updating.`));
1200
+ } else {
1201
+ spinner = ora("Creating pull request\u2026").start();
1202
+ try {
1203
+ await ensureRemoteBranch(config, targetBranch, config.baseBranch ?? "main");
1204
+ const prBody = [
1205
+ `Closes #${issueNumber}`,
1206
+ issue.body ? `
961
1207
  ${issue.body}` : "",
962
- review ? `
1208
+ review ? `
963
1209
  ## AI Review
964
1210
  ${review}` : ""
965
- ].join("\n").trim();
966
- prUrl = await createPR(config, issue.title, prBody, branch, workerBranch);
967
- spinner.stop();
968
- } catch (err) {
969
- spinner.stop();
970
- return `Committed but PR creation failed: ${err.message}`;
1211
+ ].join("\n").trim();
1212
+ prUrl = await createPR(config, issue.title, prBody, branch, targetBranch);
1213
+ spinner.stop();
1214
+ } catch (err) {
1215
+ spinner.stop();
1216
+ return `Committed but PR creation failed: ${err.message}`;
1217
+ }
971
1218
  }
972
1219
  spinner = ora("Marking as in-review\u2026").start();
973
1220
  try {
@@ -975,17 +1222,27 @@ ${review}` : ""
975
1222
  spinner.stop();
976
1223
  } catch (err) {
977
1224
  spinner.stop();
978
- return `PR created (${prUrl}) but failed to update label: ${err.message}`;
1225
+ return `PR ${existingPR ? "updated" : "created"} (${prUrl}) but failed to update label: ${err.message}`;
979
1226
  }
980
- setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0 } });
981
- return `Task #${issueNumber} submitted.
1227
+ setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0, activeBranch: void 0 } });
1228
+ return `Task #${issueNumber} ${existingPR ? "re-submitted" : "submitted"}.
982
1229
  Commit: "${commitMessage.trim()}"
983
1230
  PR: ${prUrl}`;
984
1231
  }
985
1232
  async function execute(input, config) {
986
1233
  const taskState = getConfig().taskState;
987
- const issueNumber = taskState?.activeIssueNumber;
988
- if (!issueNumber) return "No active task found. Claim a task first.";
1234
+ let issueNumber = taskState?.activeIssueNumber;
1235
+ if (!issueNumber) {
1236
+ const currentBranch = await getCurrentBranch();
1237
+ const fromBranch = parseIssueNumberFromBranch(currentBranch);
1238
+ if (fromBranch) {
1239
+ issueNumber = fromBranch;
1240
+ } else {
1241
+ const found = await getIssueNumberFromBranch(config, currentBranch);
1242
+ if (!found) return "No active task found. Claim a task first.";
1243
+ issueNumber = found.issueNumber;
1244
+ }
1245
+ }
989
1246
  const diffPromise = taskState?.baseCommit ? getDiffFromCommit(taskState.baseCommit) : getDiff();
990
1247
  const [issue, diff, branch, me] = await Promise.all([
991
1248
  getTask(config, issueNumber),
@@ -993,7 +1250,11 @@ async function execute(input, config) {
993
1250
  getCurrentBranch(),
994
1251
  getAuthenticatedUser(config)
995
1252
  ]);
996
- const workerBranch = makeWorkerBranchName(issue.author ?? me);
1253
+ const targetBranch = extractTargetBranch(issue.body) ?? makeWorkerBranchName(issue.author ?? me);
1254
+ const openSubtaskNumbers = await getOpenSubtasks(config, branch);
1255
+ if (openSubtaskNumbers.length > 0) {
1256
+ return `Cannot submit: ${openSubtaskNumbers.length} sub-task(s) still open: ` + openSubtaskNumbers.map((n) => `#${n}`).join(", ");
1257
+ }
997
1258
  const isSelfSubmit = issue.author !== null && issue.author === me;
998
1259
  let review = "";
999
1260
  if (!isSelfSubmit) {
@@ -1014,29 +1275,36 @@ async function execute(input, config) {
1014
1275
  await closeTask(config, issueNumber);
1015
1276
  } catch {
1016
1277
  }
1017
- setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0 } });
1278
+ setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0, activeBranch: void 0 } });
1018
1279
  return `Task #${issueNumber} committed and closed.
1019
1280
  Commit: "${commitMessage}"`;
1020
1281
  }
1282
+ const existingPR = await getTaskPR(config, issueNumber);
1021
1283
  let prUrl;
1022
- try {
1023
- const prBody = [
1024
- `Closes #${issueNumber}`,
1025
- issue.body ? `
1284
+ if (existingPR) {
1285
+ prUrl = existingPR.url;
1286
+ } else {
1287
+ try {
1288
+ await ensureRemoteBranch(config, targetBranch, config.baseBranch ?? "main");
1289
+ const prBody = [
1290
+ `Closes #${issueNumber}`,
1291
+ issue.body ? `
1026
1292
  ${issue.body}` : "",
1027
- review ? `
1293
+ review ? `
1028
1294
  ## AI Review
1029
1295
  ${review}` : ""
1030
- ].join("\n").trim();
1031
- prUrl = await createPR(config, issue.title, prBody, branch, workerBranch);
1032
- } catch (err) {
1033
- return `Committed but PR creation failed: ${err.message}`;
1296
+ ].join("\n").trim();
1297
+ prUrl = await createPR(config, issue.title, prBody, branch, targetBranch);
1298
+ } catch (err) {
1299
+ return `Committed but PR creation failed: ${err.message}`;
1300
+ }
1034
1301
  }
1035
1302
  try {
1036
1303
  await markInReview(config, issueNumber);
1037
1304
  } catch {
1038
1305
  }
1039
- return `Task #${issueNumber} submitted.
1306
+ setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0, activeBranch: void 0 } });
1307
+ return `Task #${issueNumber} ${existingPR ? "re-submitted" : "submitted"}.
1040
1308
  Review:
1041
1309
  ${review}
1042
1310
  Commit: "${commitMessage}"
@@ -1195,8 +1463,23 @@ async function run3(input, config) {
1195
1463
  }
1196
1464
  }
1197
1465
  const actions = [];
1198
- if (status === "available") actions.push({ name: "Claim this task", value: "claim" });
1199
- if (status === "claimed" || status === "changes-needed") actions.push({ name: "Submit this task", value: "submit" });
1466
+ if (status === "available") {
1467
+ actions.push({ name: "Claim this task", value: "claim" });
1468
+ }
1469
+ if (status === "claimed") {
1470
+ actions.push({ name: "Submit this task", value: "submit" });
1471
+ }
1472
+ if (status === "changes-needed") {
1473
+ const { getAuthenticatedUser: getAuthenticatedUser2 } = await Promise.resolve().then(() => (init_github(), github_exports));
1474
+ const me = await getAuthenticatedUser2(config);
1475
+ const taskBranch = issue.assignee ? makeTaskBranchName(issue.number, issue.assignee) : makeTaskBranchName(issue.number, me);
1476
+ const currentBranch = await getCurrentBranch();
1477
+ if (currentBranch === taskBranch) {
1478
+ actions.push({ name: "Submit this task (fixes done)", value: "submit" });
1479
+ } else {
1480
+ actions.push({ name: `Switch to ${taskBranch} to fix`, value: "switch-fix" });
1481
+ }
1482
+ }
1200
1483
  actions.push({ name: "Close this task", value: "close" });
1201
1484
  actions.push({ name: "Nothing, just viewing", value: "none" });
1202
1485
  let action;
@@ -1208,9 +1491,8 @@ async function run3(input, config) {
1208
1491
  if (action === "none") return `Viewed task #${issue.number}.`;
1209
1492
  if (action === "claim") {
1210
1493
  try {
1211
- const { getAuthenticatedUser: getAuthenticatedUser2, listMyTasks: listMyTasks2 } = await Promise.resolve().then(() => (init_github(), github_exports));
1212
- const me = await getAuthenticatedUser2(config);
1213
- const myTasks = await listMyTasks2(config, me);
1494
+ const me = await getAuthenticatedUser(config);
1495
+ const myTasks = await listMyTasks(config, me);
1214
1496
  const activeTask = myTasks.find((t) => {
1215
1497
  const labels = t.labels;
1216
1498
  return labels.includes("techunter:claimed") || labels.includes("techunter:changes-needed");
@@ -1219,33 +1501,61 @@ async function run3(input, config) {
1219
1501
  return `You already have an active task: #${activeTask.number} "${activeTask.title}"
1220
1502
  Finish or submit it before claiming a new one.`;
1221
1503
  }
1504
+ let stashed = false;
1505
+ if (await hasUncommittedChanges()) {
1506
+ let choice;
1507
+ try {
1508
+ choice = await select3({
1509
+ message: "You have uncommitted changes. What would you like to do?",
1510
+ choices: [
1511
+ { name: "Stash changes and switch branch (restore with: git stash pop)", value: "stash" },
1512
+ { name: "Cancel", value: "cancel" }
1513
+ ]
1514
+ });
1515
+ } catch {
1516
+ choice = "cancel";
1517
+ }
1518
+ if (choice === "cancel") return "Cancelled.";
1519
+ await stash(`tch: before claiming #${issue.number}`);
1520
+ stashed = true;
1521
+ console.log(chalk6.dim(" Changes stashed. Run `git stash pop` after you finish this task to restore them."));
1522
+ }
1222
1523
  let spinner = ora3(`Claiming #${issue.number}\u2026`).start();
1223
1524
  await claimTask(config, issue.number, me);
1224
1525
  spinner.stop();
1225
- const workerBranch = makeWorkerBranchName(me);
1526
+ const taskBranch = makeTaskBranchName(issue.number, me);
1226
1527
  const taskBase = extractBaseCommit(issue.body);
1227
- spinner = ora3(`Switching to ${workerBranch}${taskBase ? ` from ${taskBase.slice(0, 7)}` : ""}\u2026`).start();
1528
+ spinner = ora3(`Creating branch ${taskBranch}${taskBase ? ` from ${taskBase.slice(0, 7)}` : ""}\u2026`).start();
1228
1529
  try {
1229
1530
  if (taskBase) {
1230
- await resetOrCreateBranch(workerBranch, taskBase);
1531
+ await checkoutFromCommit(taskBranch, taskBase);
1231
1532
  } else {
1232
- await switchToBranchOrCreate(workerBranch);
1533
+ await switchToBranchOrCreate(taskBranch);
1233
1534
  }
1234
1535
  spinner.stop();
1235
- spinner = ora3("Pushing worker branch\u2026").start();
1536
+ spinner = ora3("Pushing task branch\u2026").start();
1236
1537
  try {
1237
- await pushBranch(workerBranch);
1538
+ await pushBranch(taskBranch);
1238
1539
  spinner.stop();
1239
1540
  } catch {
1240
- spinner.warn("Could not push worker branch");
1541
+ spinner.warn("Could not push task branch \u2014 will push on submit");
1241
1542
  }
1242
- } catch {
1243
- spinner.warn(`Could not switch to ${workerBranch}`);
1543
+ } catch (err) {
1544
+ spinner.warn(`Could not switch to ${taskBranch}`);
1545
+ if (stashed) {
1546
+ try {
1547
+ await stashPop();
1548
+ console.log(chalk6.dim(" Restored stashed changes."));
1549
+ } catch {
1550
+ console.log(chalk6.yellow(" Warning: could not restore stash automatically. Run `git stash pop` manually."));
1551
+ }
1552
+ }
1553
+ throw err;
1244
1554
  }
1245
1555
  const baseCommit = await getCurrentCommit();
1246
- setConfig({ taskState: { activeIssueNumber: issue.number, baseCommit } });
1556
+ setConfig({ taskState: { activeIssueNumber: issue.number, baseCommit, activeBranch: taskBranch } });
1247
1557
  console.log(chalk6.green(`
1248
- Claimed! Branch: ${workerBranch} (base: ${baseCommit.slice(0, 7)})
1558
+ Claimed! Branch: ${taskBranch} (base: ${baseCommit.slice(0, 7)})
1249
1559
  `));
1250
1560
  let openClaude;
1251
1561
  try {
@@ -1259,12 +1569,58 @@ Finish or submit it before claiming a new one.`;
1259
1569
  } catch {
1260
1570
  openClaude = false;
1261
1571
  }
1262
- if (openClaude) await launchClaudeCode(issue, workerBranch);
1263
- return `Task #${issue.number} claimed. Branch: ${workerBranch}`;
1572
+ if (openClaude) await launchClaudeCode(issue, taskBranch);
1573
+ return `Task #${issue.number} claimed. Branch: ${taskBranch}`;
1264
1574
  } catch (err) {
1265
1575
  return `Error claiming task: ${err.message}`;
1266
1576
  }
1267
1577
  }
1578
+ if (action === "switch-fix") {
1579
+ const { getAuthenticatedUser: getAuthenticatedUser2 } = await Promise.resolve().then(() => (init_github(), github_exports));
1580
+ const me = await getAuthenticatedUser2(config);
1581
+ const taskBranch = issue.assignee ? makeTaskBranchName(issue.number, issue.assignee) : makeTaskBranchName(issue.number, me);
1582
+ let stashed = false;
1583
+ if (await hasUncommittedChanges()) {
1584
+ let choice;
1585
+ try {
1586
+ choice = await select3({
1587
+ message: "You have uncommitted changes. What would you like to do?",
1588
+ choices: [
1589
+ { name: "Stash changes and switch branch (restore with: git stash pop)", value: "stash" },
1590
+ { name: "Cancel", value: "cancel" }
1591
+ ]
1592
+ });
1593
+ } catch {
1594
+ choice = "cancel";
1595
+ }
1596
+ if (choice === "cancel") return "Cancelled.";
1597
+ await stash(`tch: before switching to ${taskBranch}`);
1598
+ stashed = true;
1599
+ console.log(chalk6.dim(" Changes stashed. Run `git stash pop` to restore them later."));
1600
+ }
1601
+ const spinner = ora3(`Switching to ${taskBranch}\u2026`).start();
1602
+ try {
1603
+ await switchToBranchOrCreate(taskBranch);
1604
+ spinner.stop();
1605
+ } catch (err) {
1606
+ spinner.warn(`Could not switch to ${taskBranch}: ${err.message}`);
1607
+ if (stashed) {
1608
+ try {
1609
+ await stashPop();
1610
+ console.log(chalk6.dim(" Restored stashed changes."));
1611
+ } catch {
1612
+ console.log(chalk6.yellow(" Run `git stash pop` manually to restore your changes."));
1613
+ }
1614
+ }
1615
+ return `Error: ${err.message}`;
1616
+ }
1617
+ const baseCommit = extractBaseCommit(issue.body) ?? await getCurrentCommit();
1618
+ setConfig({ taskState: { activeIssueNumber: issue.number, baseCommit, activeBranch: taskBranch } });
1619
+ console.log(chalk6.green(`
1620
+ Switched to ${taskBranch}. Fix the issues then run /submit.
1621
+ `));
1622
+ return `Switched to ${taskBranch} for task #${issue.number}.`;
1623
+ }
1268
1624
  if (action === "submit") return run({}, config);
1269
1625
  if (action === "close") return run2({ issue_number: issue.number }, config);
1270
1626
  return "Cancelled.";
@@ -1293,28 +1649,31 @@ async function execute3(input, config) {
1293
1649
  if (activeTask) {
1294
1650
  return `You already have an active task: #${activeTask.number} "${activeTask.title}". Finish it before claiming a new one.`;
1295
1651
  }
1652
+ if (await hasUncommittedChanges()) {
1653
+ return "Cannot claim: you have uncommitted changes. Commit or stash them first (git stash).";
1654
+ }
1296
1655
  try {
1297
1656
  await claimTask(config, issueNumber, me);
1298
1657
  } catch (err) {
1299
1658
  return `Error claiming task: ${err.message}`;
1300
1659
  }
1301
- const workerBranch = makeWorkerBranchName(me);
1660
+ const taskBranch = makeTaskBranchName(issue.number, me);
1302
1661
  const taskBase = extractBaseCommit(issue.body);
1303
1662
  try {
1304
1663
  if (taskBase) {
1305
- await resetOrCreateBranch(workerBranch, taskBase);
1664
+ await checkoutFromCommit(taskBranch, taskBase);
1306
1665
  } else {
1307
- await switchToBranchOrCreate(workerBranch);
1666
+ await switchToBranchOrCreate(taskBranch);
1308
1667
  }
1309
1668
  } catch {
1310
1669
  }
1311
1670
  try {
1312
- await pushBranch(workerBranch);
1671
+ await pushBranch(taskBranch);
1313
1672
  } catch {
1314
1673
  }
1315
1674
  const baseCommit = await getCurrentCommit();
1316
- setConfig({ taskState: { activeIssueNumber: issueNumber, baseCommit } });
1317
- return `Task #${issueNumber} claimed. Branch: ${workerBranch} (base commit: ${baseCommit.slice(0, 7)})`;
1675
+ setConfig({ taskState: { activeIssueNumber: issueNumber, baseCommit, activeBranch: taskBranch } });
1676
+ return `Task #${issueNumber} claimed. Branch: ${taskBranch} (base commit: ${baseCommit.slice(0, 7)})`;
1318
1677
  }
1319
1678
  return `Unknown action: ${action}`;
1320
1679
  }
@@ -1389,6 +1748,77 @@ async function openInEditor(content) {
1389
1748
  await rm(dir, { recursive: true, force: true });
1390
1749
  }
1391
1750
  }
1751
+ async function resolveBaseAndTarget(config, me, interactive) {
1752
+ const currentBranch = await getCurrentBranch();
1753
+ if (isTaskBranch(currentBranch)) {
1754
+ if (await hasUncommittedChanges()) {
1755
+ if (!interactive) {
1756
+ throw new Error("Cannot create sub-task: you have uncommitted changes. Commit them first so the executor starts from the correct base.");
1757
+ }
1758
+ const { select: inquirerSelect } = await import("@inquirer/prompts");
1759
+ let choice;
1760
+ try {
1761
+ choice = await inquirerSelect({
1762
+ message: "You have uncommitted changes. The sub-task executor will start from the last commit \u2014 they won't see your current unsaved work.",
1763
+ choices: [
1764
+ { name: "Commit first (cancel and commit manually)", value: "cancel" },
1765
+ { name: "Continue anyway (executor starts without my unsaved changes)", value: "continue" }
1766
+ ]
1767
+ });
1768
+ } catch {
1769
+ choice = "cancel";
1770
+ }
1771
+ if (choice === "cancel") throw new Error("Cancelled. Commit your changes first, then create the sub-task.");
1772
+ }
1773
+ const baseCommit2 = await getCurrentCommit();
1774
+ return { baseCommit: baseCommit2, targetBranch: currentBranch, isSubtask: true };
1775
+ }
1776
+ let stashedForSync = false;
1777
+ if (await hasUncommittedChanges()) {
1778
+ if (!interactive) {
1779
+ throw new Error("Cannot create task: you have uncommitted changes. Commit or stash them first (git stash).");
1780
+ }
1781
+ const { select: inquirerSelect } = await import("@inquirer/prompts");
1782
+ let choice;
1783
+ try {
1784
+ choice = await inquirerSelect({
1785
+ message: "You have uncommitted changes. Syncing with main requires a clean working tree.",
1786
+ choices: [
1787
+ { name: "Stash changes and continue (restore with: git stash pop)", value: "stash" },
1788
+ { name: "Cancel", value: "cancel" }
1789
+ ]
1790
+ });
1791
+ } catch {
1792
+ choice = "cancel";
1793
+ }
1794
+ if (choice === "cancel") throw new Error("Cancelled.");
1795
+ await stash("tch: before creating new task");
1796
+ stashedForSync = true;
1797
+ console.log(chalk7.dim(" Changes stashed. Run `git stash pop` after creating the task."));
1798
+ }
1799
+ const baseBranch = config.baseBranch ?? "main";
1800
+ let baseCommit;
1801
+ const syncSpinner = ora4(`Syncing with ${baseBranch}\u2026`).start();
1802
+ try {
1803
+ await syncWithBase(baseBranch);
1804
+ baseCommit = await getCurrentCommit();
1805
+ syncSpinner.succeed(`Synced with ${baseBranch} (base: ${baseCommit.slice(0, 7)})`);
1806
+ } catch {
1807
+ syncSpinner.warn(`Could not sync with ${baseBranch} \u2014 recording remote HEAD as base`);
1808
+ try {
1809
+ baseCommit = await getRemoteHeadSha(baseBranch);
1810
+ } catch {
1811
+ }
1812
+ if (stashedForSync) {
1813
+ try {
1814
+ await stashPop();
1815
+ } catch {
1816
+ }
1817
+ throw new Error(`Could not sync with ${baseBranch}. Your changes have been restored from stash.`);
1818
+ }
1819
+ }
1820
+ return { baseCommit, targetBranch: makeWorkerBranchName(me), isSubtask: false };
1821
+ }
1392
1822
  var definition4 = {
1393
1823
  type: "function",
1394
1824
  function: {
@@ -1484,26 +1914,23 @@ async function run4(input, config) {
1484
1914
  console.log(chalk7.yellow(` Revision error: ${err.message}`));
1485
1915
  }
1486
1916
  }
1487
- const baseBranch = config.baseBranch ?? "main";
1488
1917
  let baseCommit;
1489
- const syncSpinner = ora4(`Syncing with ${baseBranch}\u2026`).start();
1918
+ let targetBranch;
1919
+ let isSubtask;
1490
1920
  try {
1491
- await syncWithBase(baseBranch);
1492
- baseCommit = await getCurrentCommit();
1493
- syncSpinner.succeed(`Synced with ${baseBranch} (base: ${baseCommit.slice(0, 7)})`);
1494
- } catch {
1495
- syncSpinner.warn(`Could not sync with ${baseBranch} \u2014 recording remote HEAD as base`);
1496
- try {
1497
- baseCommit = await getRemoteHeadSha(baseBranch);
1498
- } catch {
1499
- }
1921
+ ({ baseCommit, targetBranch, isSubtask } = await resolveBaseAndTarget(config, me, true));
1922
+ } catch (err) {
1923
+ return err.message;
1924
+ }
1925
+ if (isSubtask) {
1926
+ console.log(chalk7.dim(` Sub-task: will target branch ${chalk7.cyan(targetBranch)} (base: ${baseCommit?.slice(0, 7) ?? "HEAD"})`));
1500
1927
  }
1501
1928
  const createSpinner = ora4(`Creating "${title}"\u2026`).start();
1502
1929
  let htmlUrl;
1503
1930
  let issueNumber;
1504
1931
  let issueTitle;
1505
1932
  try {
1506
- const issue = await createTask(config, title, guide, baseCommit);
1933
+ const issue = await createTask(config, title, guide, baseCommit, targetBranch);
1507
1934
  createSpinner.stop();
1508
1935
  htmlUrl = issue.htmlUrl;
1509
1936
  issueNumber = issue.number;
@@ -1540,19 +1967,9 @@ async function execute4(input, config) {
1540
1967
  if (feedback) {
1541
1968
  guide = await generateGuide(config, title, { feedback, previousGuide: guide });
1542
1969
  }
1543
- const baseBranch = config.baseBranch ?? "main";
1544
- let baseCommit;
1545
- try {
1546
- await syncWithBase(baseBranch);
1547
- baseCommit = await getCurrentCommit();
1548
- } catch {
1549
- try {
1550
- baseCommit = await getRemoteHeadSha(baseBranch);
1551
- } catch {
1552
- }
1553
- }
1970
+ const { baseCommit, targetBranch } = await resolveBaseAndTarget(config, me, false);
1554
1971
  try {
1555
- const issue = await createTask(config, title, guide, baseCommit);
1972
+ const issue = await createTask(config, title, guide, baseCommit, targetBranch);
1556
1973
  return `Created #${issue.number} "${issue.title}" \u2014 ${issue.htmlUrl}
1557
1974
 
1558
1975
  Guide:
@@ -1602,119 +2019,249 @@ var terminal5 = true;
1602
2019
  // src/tools/review/index.ts
1603
2020
  var review_exports = {};
1604
2021
  __export(review_exports, {
2022
+ definition: () => definition8,
2023
+ execute: () => execute8,
2024
+ run: () => run8,
2025
+ terminal: () => terminal8
2026
+ });
2027
+ init_github();
2028
+ import chalk10 from "chalk";
2029
+ import ora8 from "ora";
2030
+ import { select as select7 } from "@inquirer/prompts";
2031
+
2032
+ // src/tools/accept/index.ts
2033
+ var accept_exports = {};
2034
+ __export(accept_exports, {
1605
2035
  definition: () => definition6,
1606
2036
  execute: () => execute6,
1607
2037
  run: () => run6,
1608
2038
  terminal: () => terminal6
1609
2039
  });
1610
2040
  init_github();
2041
+ import chalk8 from "chalk";
2042
+ import { select as select5 } from "@inquirer/prompts";
1611
2043
  import ora6 from "ora";
1612
- var definition6 = {
1613
- type: "function",
1614
- function: {
1615
- name: "review",
1616
- description: "List tasks waiting for your review (submitted by others, created by you). Equivalent to /review.",
1617
- parameters: { type: "object", properties: {}, required: [] }
2044
+
2045
+ // src/tools/wiki/prompts.ts
2046
+ var WIKI_FORMAT = `
2047
+ The document you produce must be valid Markdown with these exact sections:
2048
+
2049
+ # [Project Name]
2050
+
2051
+ > One-sentence description of what this project does.
2052
+
2053
+ ## What Is This?
2054
+
2055
+ 2-4 paragraphs covering:
2056
+ - The problem this project solves
2057
+ - Who uses it and in what context
2058
+ - Core capabilities / key features
2059
+
2060
+ ## Quick Start
2061
+
2062
+ Numbered steps for a brand-new developer to install, configure, and run the project for the first time.
2063
+
2064
+ ## Architecture
2065
+
2066
+ High-level explanation of how the system is structured:
2067
+ - Key components / layers and their responsibilities
2068
+ - Request or data flow (prose or ASCII diagram)
2069
+ - Noteworthy design decisions
2070
+
2071
+ ## Key Files
2072
+
2073
+ | File / Directory | Purpose |
2074
+ |---|---|
2075
+ | ... | ... |
2076
+
2077
+ (List the 8-15 most important files.)
2078
+
2079
+ ## Development Workflow
2080
+
2081
+ Common day-to-day tasks a contributor will need:
2082
+ - How to build / run locally
2083
+ - How to add a new feature (brief steps)
2084
+ - Any testing or linting commands
2085
+
2086
+ ---
2087
+ *Maintained by Techunter \u2014 run \`tch wiki\` to regenerate*
2088
+ `;
2089
+
2090
+ // src/tools/wiki/wiki-generator.ts
2091
+ async function generateWiki(config) {
2092
+ return runSubAgentLoop(
2093
+ config,
2094
+ "You are a senior engineer writing a project overview document for new team members. Use list_files to understand the project structure, then grep_code and run_command to read key files (e.g. package.json, README, entry points, config files). Be concrete and specific \u2014 reference real file names, commands, and concepts from this codebase. Avoid vague filler. When you have enough context, write the document.\n\n" + WIKI_FORMAT,
2095
+ "Analyze this project thoroughly and produce a comprehensive TECHUNTER.md overview document for new team members.",
2096
+ ["list_files", "grep_code", "run_command"]
2097
+ );
2098
+ }
2099
+
2100
+ // src/tools/accept/index.ts
2101
+ var definition6 = {
2102
+ type: "function",
2103
+ function: {
2104
+ name: "accept",
2105
+ description: "Accept an in-review task: merges the PR into the target branch and closes the issue.",
2106
+ parameters: {
2107
+ type: "object",
2108
+ properties: {
2109
+ issue_number: { type: "number", description: "GitHub issue number to accept" }
2110
+ },
2111
+ required: ["issue_number"]
2112
+ }
1618
2113
  }
1619
2114
  };
1620
- async function run6(_input, config) {
1621
- const spinner = ora6("Loading tasks for review\u2026").start();
2115
+ async function run6(input, config) {
2116
+ let issueNumber = input["issue_number"];
2117
+ if (!issueNumber) {
2118
+ const spinner3 = ora6("Loading tasks for review\u2026").start();
2119
+ let tasks;
2120
+ let me;
2121
+ try {
2122
+ me = await getAuthenticatedUser(config);
2123
+ tasks = await listTasksForReview(config, me);
2124
+ spinner3.stop();
2125
+ } catch (err) {
2126
+ spinner3.stop();
2127
+ return `Error: ${err.message}`;
2128
+ }
2129
+ if (tasks.length === 0) return "No tasks pending review.";
2130
+ try {
2131
+ issueNumber = await select5({
2132
+ message: "Which task to accept?",
2133
+ choices: tasks.map((t) => ({
2134
+ name: `#${t.number} @${t.assignee ?? "\u2014"} ${t.title}`,
2135
+ value: t.number
2136
+ }))
2137
+ });
2138
+ } catch {
2139
+ return "Cancelled.";
2140
+ }
2141
+ }
2142
+ const spinner2 = ora6("Verifying permissions\u2026").start();
2143
+ let me2;
2144
+ let issue;
1622
2145
  try {
1623
- const me = await getAuthenticatedUser(config);
1624
- const tasks = await listTasksForReview(config, me);
1625
- spinner.stop();
1626
- if (tasks.length === 0) return `No tasks pending review for @${me}.`;
1627
- const lines = tasks.map((t) => ` #${t.number} [in-review] @${t.assignee ?? "\u2014"} ${t.title}`);
1628
- return `Tasks pending review (created by @${me}):
1629
- ${lines.join("\n")}`;
2146
+ [me2, issue] = await Promise.all([
2147
+ getAuthenticatedUser(config),
2148
+ getTask(config, issueNumber)
2149
+ ]);
2150
+ spinner2.stop();
1630
2151
  } catch (err) {
1631
- spinner.stop();
2152
+ spinner2.stop();
1632
2153
  return `Error: ${err.message}`;
1633
2154
  }
1634
- }
1635
- var execute6 = run6;
1636
- var terminal6 = true;
1637
-
1638
- // src/tools/refresh/index.ts
1639
- var refresh_exports = {};
1640
- __export(refresh_exports, {
1641
- definition: () => definition7,
1642
- execute: () => execute7,
1643
- run: () => run7,
1644
- terminal: () => terminal7
1645
- });
1646
- var definition7 = {
1647
- type: "function",
1648
- function: {
1649
- name: "refresh",
1650
- description: "Reload and display the full task list. Equivalent to /refresh.",
1651
- parameters: { type: "object", properties: {}, required: [] }
2155
+ if (issue.author && issue.author !== me2) {
2156
+ return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
1652
2157
  }
1653
- };
1654
- async function run7(_input, config) {
1655
- const tasks = await printTaskList(config);
1656
- if (tasks.length === 0) return "No tasks found.";
1657
- const lines = tasks.map((t) => {
1658
- const status = getStatus(t);
1659
- const assignee = t.assignee ? `@${t.assignee}` : "\u2014";
1660
- return `#${t.number} [${status}] ${assignee} ${t.title}`;
1661
- });
1662
- return `Tasks (${tasks.length}):
1663
- ${lines.join("\n")}`;
1664
- }
1665
- var execute7 = run7;
1666
- var terminal7 = true;
1667
-
1668
- // src/tools/open-code/index.ts
1669
- var open_code_exports = {};
1670
- __export(open_code_exports, {
1671
- definition: () => definition8,
1672
- execute: () => execute8,
1673
- run: () => run8,
1674
- terminal: () => terminal8
1675
- });
1676
- init_github();
1677
- var definition8 = {
1678
- type: "function",
1679
- function: {
1680
- name: "open_code",
1681
- description: "Launch Claude Code for the current task branch. Equivalent to /code.",
1682
- parameters: { type: "object", properties: {}, required: [] }
2158
+ let confirmed;
2159
+ try {
2160
+ confirmed = await select5({
2161
+ message: `Merge PR for #${issueNumber} and close issue?`,
2162
+ choices: [
2163
+ { name: "Yes, accept", value: true },
2164
+ { name: "Cancel", value: false }
2165
+ ]
2166
+ });
2167
+ } catch {
2168
+ return "Cancelled.";
1683
2169
  }
1684
- };
1685
- async function run8(_input, config) {
1686
- let branch;
2170
+ if (!confirmed) return "Cancelled.";
2171
+ const spinner = ora6(`Merging PR for #${issueNumber}\u2026`).start();
2172
+ let result;
1687
2173
  try {
1688
- branch = await getCurrentBranch();
2174
+ result = await acceptTask(config, issueNumber);
2175
+ spinner.succeed(`PR #${result.prNumber} merged \u2192 ${chalk8.cyan(result.baseBranch)}`);
1689
2176
  } catch (err) {
2177
+ spinner.fail("Failed");
1690
2178
  return `Error: ${err.message}`;
1691
2179
  }
1692
- const match = branch.match(/^task-(\d+)-/);
1693
- if (!match) return `Not on a task branch (current: ${branch}).`;
1694
- const issueNum = parseInt(match[1], 10);
1695
- let issue;
2180
+ const mergedIntoTaskBranch = isTaskBranch(result.baseBranch);
2181
+ if (!mergedIntoTaskBranch) {
2182
+ const baseBranch = config.baseBranch ?? "main";
2183
+ let pushToMain;
2184
+ try {
2185
+ pushToMain = await select5({
2186
+ message: `Push ${chalk8.cyan(result.baseBranch)} \u2192 ${chalk8.cyan(baseBranch)}?`,
2187
+ choices: [
2188
+ { name: `Yes, push to ${baseBranch}`, value: true },
2189
+ { name: "No, keep in worker branch", value: false }
2190
+ ]
2191
+ });
2192
+ } catch {
2193
+ pushToMain = false;
2194
+ }
2195
+ if (pushToMain) {
2196
+ const mergeSpinner = ora6(`Merging ${result.baseBranch} \u2192 ${baseBranch}\u2026`).start();
2197
+ try {
2198
+ await mergeWorkerIntoBase(config, result.baseBranch, baseBranch);
2199
+ mergeSpinner.succeed(`Merged ${result.baseBranch} \u2192 ${baseBranch}`);
2200
+ } catch (err) {
2201
+ mergeSpinner.fail(`Could not merge to ${baseBranch}: ${err.message}`);
2202
+ }
2203
+ }
2204
+ }
2205
+ let updateWiki = false;
1696
2206
  try {
1697
- issue = await getTask(config, issueNum);
2207
+ updateWiki = await select5({
2208
+ message: "Update TECHUNTER.md project overview?",
2209
+ choices: [
2210
+ { name: "Yes, regenerate", value: true },
2211
+ { name: "No, skip", value: false }
2212
+ ]
2213
+ });
2214
+ } catch {
2215
+ }
2216
+ if (updateWiki) {
2217
+ const wikiSpinner = ora6("Regenerating TECHUNTER.md\u2026").start();
2218
+ try {
2219
+ const content = await generateWiki(config);
2220
+ await upsertRepoFile(config, "TECHUNTER.md", content, "docs: update TECHUNTER.md project overview");
2221
+ wikiSpinner.succeed("TECHUNTER.md updated");
2222
+ } catch (err) {
2223
+ wikiSpinner.fail(`Wiki update failed: ${err.message}`);
2224
+ }
2225
+ }
2226
+ const mergeTarget = mergedIntoTaskBranch ? `${result.baseBranch} (sub-task merged, no push to main)` : result.baseBranch;
2227
+ return `Task #${issueNumber} accepted.
2228
+ PR #${result.prNumber} merged \u2192 ${mergeTarget}
2229
+ Issue closed.`;
2230
+ }
2231
+ async function execute6(input, config) {
2232
+ const issueNumber = input["issue_number"];
2233
+ const [me, issue] = await Promise.all([
2234
+ getAuthenticatedUser(config),
2235
+ getTask(config, issueNumber)
2236
+ ]);
2237
+ if (issue.author && issue.author !== me) {
2238
+ return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
2239
+ }
2240
+ const spinner = ora6(`Merging PR for #${issueNumber}\u2026`).start();
2241
+ try {
2242
+ const result = await acceptTask(config, issueNumber);
2243
+ spinner.stop();
2244
+ return `Task #${issueNumber} accepted.
2245
+ PR #${result.prNumber} merged \u2192 ${result.baseBranch}
2246
+ Issue closed.`;
1698
2247
  } catch (err) {
2248
+ spinner.stop();
1699
2249
  return `Error: ${err.message}`;
1700
2250
  }
1701
- await launchClaudeCode(issue, branch);
1702
- return "Claude Code session ended.";
1703
2251
  }
1704
- var execute8 = run8;
1705
- var terminal8 = true;
2252
+ var terminal6 = true;
1706
2253
 
1707
2254
  // src/tools/reject/index.ts
1708
2255
  var reject_exports = {};
1709
2256
  __export(reject_exports, {
1710
- definition: () => definition9,
1711
- execute: () => execute9,
1712
- run: () => run9,
1713
- terminal: () => terminal9
2257
+ definition: () => definition7,
2258
+ execute: () => execute7,
2259
+ run: () => run7,
2260
+ terminal: () => terminal7
1714
2261
  });
1715
2262
  init_github();
1716
- import chalk8 from "chalk";
1717
- import { select as select5, input as promptInput3 } from "@inquirer/prompts";
2263
+ import chalk9 from "chalk";
2264
+ import { select as select6, input as promptInput3 } from "@inquirer/prompts";
1718
2265
  import ora7 from "ora";
1719
2266
 
1720
2267
  // src/tools/reject/prompts.ts
@@ -1746,7 +2293,7 @@ Reviewer feedback: ${userFeedback}`,
1746
2293
  }
1747
2294
 
1748
2295
  // src/tools/reject/index.ts
1749
- var definition9 = {
2296
+ var definition7 = {
1750
2297
  type: "function",
1751
2298
  function: {
1752
2299
  name: "reject",
@@ -1761,7 +2308,7 @@ var definition9 = {
1761
2308
  }
1762
2309
  }
1763
2310
  };
1764
- async function run9(input, config) {
2311
+ async function run7(input, config) {
1765
2312
  const issueNumber = input["issue_number"];
1766
2313
  const [me, issue] = await Promise.all([
1767
2314
  getAuthenticatedUser(config),
@@ -1779,7 +2326,7 @@ async function run9(input, config) {
1779
2326
  return "Cancelled.";
1780
2327
  }
1781
2328
  if (!feedback.trim()) return "Cancelled.";
1782
- const divider = chalk8.dim("\u2500".repeat(70));
2329
+ const divider = chalk9.dim("\u2500".repeat(70));
1783
2330
  for (; ; ) {
1784
2331
  const spinner = ora7("Generating rejection comment\u2026").start();
1785
2332
  let comment;
@@ -1791,13 +2338,13 @@ async function run9(input, config) {
1791
2338
  return `Error generating comment: ${err.message}`;
1792
2339
  }
1793
2340
  console.log("\n" + divider);
1794
- console.log(chalk8.bold(` Rejection preview \u2014 issue #${issueNumber}`));
2341
+ console.log(chalk9.bold(` Rejection preview \u2014 issue #${issueNumber}`));
1795
2342
  console.log(divider);
1796
2343
  console.log(renderMarkdown(comment));
1797
2344
  console.log(divider + "\n");
1798
2345
  let decision;
1799
2346
  try {
1800
- decision = await select5({
2347
+ decision = await select6({
1801
2348
  message: `Post rejection and mark #${issueNumber} as changes-needed?`,
1802
2349
  choices: [
1803
2350
  { name: "Post & Reject", value: "yes" },
@@ -1836,7 +2383,7 @@ async function run9(input, config) {
1836
2383
  return `Task #${issueNumber} rejected. Label changed to changes-needed.`;
1837
2384
  }
1838
2385
  }
1839
- async function execute9(input, config) {
2386
+ async function execute7(input, config) {
1840
2387
  const issueNumber = input["issue_number"];
1841
2388
  const feedback = input["feedback"];
1842
2389
  const [me, issue] = await Promise.all([
@@ -1867,150 +2414,181 @@ async function execute9(input, config) {
1867
2414
  Comment posted:
1868
2415
  ${comment}`;
1869
2416
  }
1870
- var terminal9 = true;
2417
+ var terminal7 = true;
1871
2418
 
1872
- // src/tools/accept/index.ts
1873
- var accept_exports = {};
1874
- __export(accept_exports, {
1875
- definition: () => definition10,
1876
- execute: () => execute10,
1877
- run: () => run10,
1878
- terminal: () => terminal10
1879
- });
1880
- init_github();
1881
- import chalk9 from "chalk";
1882
- import { select as select6 } from "@inquirer/prompts";
1883
- import ora8 from "ora";
1884
- var definition10 = {
2419
+ // src/tools/review/index.ts
2420
+ var definition8 = {
1885
2421
  type: "function",
1886
2422
  function: {
1887
- name: "accept",
1888
- description: "Accept an in-review task: merges the PR into your worker branch and closes the issue.",
1889
- parameters: {
1890
- type: "object",
1891
- properties: {
1892
- issue_number: { type: "number", description: "GitHub issue number to accept" }
1893
- },
1894
- required: ["issue_number"]
1895
- }
2423
+ name: "review",
2424
+ description: "List tasks waiting for your review (submitted by others, created by you), then let you accept or reject one. Equivalent to /review.",
2425
+ parameters: { type: "object", properties: {}, required: [] }
1896
2426
  }
1897
2427
  };
1898
- async function run10(input, config) {
1899
- let issueNumber = input["issue_number"];
1900
- if (!issueNumber) {
1901
- const spinner3 = ora8("Loading tasks for review\u2026").start();
1902
- let tasks;
1903
- let me;
1904
- try {
1905
- me = await getAuthenticatedUser(config);
1906
- tasks = await listTasksForReview(config, me);
1907
- spinner3.stop();
1908
- } catch (err) {
1909
- spinner3.stop();
1910
- return `Error: ${err.message}`;
1911
- }
1912
- if (tasks.length === 0) return "No tasks pending review.";
1913
- try {
1914
- issueNumber = await select6({
1915
- message: "Which task to accept?",
1916
- choices: tasks.map((t) => ({
1917
- name: `#${t.number} @${t.assignee ?? "\u2014"} ${t.title}`,
1918
- value: t.number
1919
- }))
1920
- });
1921
- } catch {
1922
- return "Cancelled.";
1923
- }
2428
+ async function run8(_input, config) {
2429
+ const spinner = ora8("Loading tasks for review\u2026").start();
2430
+ let me;
2431
+ let tasks;
2432
+ try {
2433
+ me = await getAuthenticatedUser(config);
2434
+ tasks = await listTasksForReview(config, me);
2435
+ spinner.stop();
2436
+ } catch (err) {
2437
+ spinner.stop();
2438
+ return `Error: ${err.message}`;
1924
2439
  }
1925
- const spinner2 = ora8("Verifying permissions\u2026").start();
1926
- let me2;
1927
- let issue;
2440
+ if (tasks.length === 0) return `No tasks pending review for @${me}.`;
2441
+ let issueNumber;
1928
2442
  try {
1929
- [me2, issue] = await Promise.all([
1930
- getAuthenticatedUser(config),
1931
- getTask(config, issueNumber)
1932
- ]);
2443
+ issueNumber = await select7({
2444
+ message: "Select a task to review:",
2445
+ choices: tasks.map((t) => ({
2446
+ name: `#${String(t.number).padEnd(4)} @${t.assignee ?? "\u2014"} ${t.title}`,
2447
+ value: t.number
2448
+ }))
2449
+ });
2450
+ } catch {
2451
+ return "Cancelled.";
2452
+ }
2453
+ const spinner2 = ora8(`Loading #${issueNumber}\u2026`).start();
2454
+ let pr;
2455
+ try {
2456
+ pr = await getTaskPR(config, issueNumber);
1933
2457
  spinner2.stop();
1934
2458
  } catch (err) {
1935
2459
  spinner2.stop();
1936
- return `Error: ${err.message}`;
2460
+ return `Error loading PR: ${err.message}`;
1937
2461
  }
1938
- if (issue.author && issue.author !== me2) {
1939
- return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
2462
+ const divider = chalk10.dim("\u2500".repeat(70));
2463
+ console.log("\n" + divider);
2464
+ if (pr) {
2465
+ console.log(chalk10.bold(` PR #${pr.number}`) + " " + chalk10.dim(pr.url));
2466
+ console.log(divider);
2467
+ console.log(renderMarkdown(pr.body));
2468
+ } else {
2469
+ console.log(chalk10.yellow(` No open PR found for task #${issueNumber}`));
2470
+ }
2471
+ console.log(divider + "\n");
2472
+ for (; ; ) {
2473
+ let action;
2474
+ try {
2475
+ action = await select7({
2476
+ message: "Review action:",
2477
+ choices: [
2478
+ ...pr ? [{ name: "View diff", value: "diff" }] : [],
2479
+ { name: chalk10.green("Accept") + " \u2014 merge PR and close issue", value: "accept" },
2480
+ { name: chalk10.red("Reject") + " \u2014 request changes", value: "reject" },
2481
+ { name: "Nothing, just viewing", value: "none" }
2482
+ ]
2483
+ });
2484
+ } catch {
2485
+ return "Cancelled.";
2486
+ }
2487
+ if (action === "none") return `Viewed task #${issueNumber}.`;
2488
+ if (action === "accept") return run6({ issue_number: issueNumber }, config);
2489
+ if (action === "reject") return run7({ issue_number: issueNumber }, config);
2490
+ if (action === "diff") {
2491
+ const diffSpinner = ora8("Fetching diff\u2026").start();
2492
+ let diff;
2493
+ try {
2494
+ diff = await getTaskPRDiff(config, pr.number);
2495
+ diffSpinner.stop();
2496
+ } catch (err) {
2497
+ diffSpinner.stop();
2498
+ console.log(chalk10.red(`Error fetching diff: ${err.message}`));
2499
+ continue;
2500
+ }
2501
+ console.log("\n" + divider);
2502
+ for (const line of diff.split("\n")) {
2503
+ if (line.startsWith("+") && !line.startsWith("+++")) {
2504
+ process.stdout.write(chalk10.green(line) + "\n");
2505
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
2506
+ process.stdout.write(chalk10.red(line) + "\n");
2507
+ } else if (line.startsWith("@@")) {
2508
+ process.stdout.write(chalk10.cyan(line) + "\n");
2509
+ } else if (line.startsWith("diff ") || line.startsWith("index ") || line.startsWith("+++") || line.startsWith("---")) {
2510
+ process.stdout.write(chalk10.bold(line) + "\n");
2511
+ } else {
2512
+ process.stdout.write(line + "\n");
2513
+ }
2514
+ }
2515
+ console.log(divider + "\n");
2516
+ }
2517
+ }
2518
+ }
2519
+ var execute8 = run8;
2520
+ var terminal8 = true;
2521
+
2522
+ // src/tools/refresh/index.ts
2523
+ var refresh_exports = {};
2524
+ __export(refresh_exports, {
2525
+ definition: () => definition9,
2526
+ execute: () => execute9,
2527
+ run: () => run9,
2528
+ terminal: () => terminal9
2529
+ });
2530
+ var definition9 = {
2531
+ type: "function",
2532
+ function: {
2533
+ name: "refresh",
2534
+ description: "Reload and display the full task list. Equivalent to /refresh.",
2535
+ parameters: { type: "object", properties: {}, required: [] }
1940
2536
  }
1941
- const targetBranch = makeWorkerBranchName(me2);
1942
- let confirmed;
1943
- try {
1944
- confirmed = await select6({
1945
- message: `Merge PR for #${issueNumber} into ${chalk9.cyan(targetBranch)} and close issue?`,
1946
- choices: [
1947
- { name: "Yes, accept", value: true },
1948
- { name: "Cancel", value: false }
1949
- ]
1950
- });
1951
- } catch {
1952
- return "Cancelled.";
2537
+ };
2538
+ async function run9(_input, config) {
2539
+ const tasks = await printTaskList(config);
2540
+ if (tasks.length === 0) return "No tasks found.";
2541
+ const lines = tasks.map((t) => {
2542
+ const status = getStatus(t);
2543
+ const assignee = t.assignee ? `@${t.assignee}` : "\u2014";
2544
+ return `#${t.number} [${status}] ${assignee} ${t.title}`;
2545
+ });
2546
+ return `Tasks (${tasks.length}):
2547
+ ${lines.join("\n")}`;
2548
+ }
2549
+ var execute9 = run9;
2550
+ var terminal9 = true;
2551
+
2552
+ // src/tools/open-code/index.ts
2553
+ var open_code_exports = {};
2554
+ __export(open_code_exports, {
2555
+ definition: () => definition10,
2556
+ execute: () => execute10,
2557
+ run: () => run10,
2558
+ terminal: () => terminal10
2559
+ });
2560
+ init_github();
2561
+ var definition10 = {
2562
+ type: "function",
2563
+ function: {
2564
+ name: "open_code",
2565
+ description: "Launch Claude Code for the current task branch. Equivalent to /code.",
2566
+ parameters: { type: "object", properties: {}, required: [] }
1953
2567
  }
1954
- if (!confirmed) return "Cancelled.";
1955
- const spinner = ora8(`Merging PR for #${issueNumber}\u2026`).start();
1956
- let result;
2568
+ };
2569
+ async function run10(_input, config) {
2570
+ let branch;
1957
2571
  try {
1958
- const assigneeWorkerBranch = issue.assignee ? makeWorkerBranchName(issue.assignee) : void 0;
1959
- result = await acceptTask(config, issueNumber, assigneeWorkerBranch);
1960
- spinner.succeed(`PR #${result.prNumber} merged into ${targetBranch}`);
2572
+ branch = await getCurrentBranch();
1961
2573
  } catch (err) {
1962
- spinner.fail("Failed");
1963
2574
  return `Error: ${err.message}`;
1964
2575
  }
1965
- const baseBranch = config.baseBranch ?? "main";
1966
- let pushToMain;
1967
- try {
1968
- pushToMain = await select6({
1969
- message: `Push ${chalk9.cyan(targetBranch)} \u2192 ${chalk9.cyan(baseBranch)}?`,
1970
- choices: [
1971
- { name: `Yes, push to ${baseBranch}`, value: true },
1972
- { name: "No, keep in worker branch", value: false }
1973
- ]
1974
- });
1975
- } catch {
1976
- pushToMain = false;
1977
- }
1978
- if (pushToMain) {
1979
- const mergeSpinner = ora8(`Merging ${targetBranch} \u2192 ${baseBranch}\u2026`).start();
1980
- try {
1981
- await mergeWorkerIntoBase(config, targetBranch, baseBranch);
1982
- mergeSpinner.succeed(`Merged ${targetBranch} \u2192 ${baseBranch}`);
1983
- } catch (err) {
1984
- mergeSpinner.fail(`Could not merge to ${baseBranch}: ${err.message}`);
1985
- }
1986
- }
1987
- return `Task #${issueNumber} accepted.
1988
- PR #${result.prNumber} merged \u2192 ${targetBranch}${pushToMain ? ` \u2192 ${baseBranch}` : ""}
1989
- Issue closed.`;
1990
- }
1991
- async function execute10(input, config) {
1992
- const issueNumber = input["issue_number"];
1993
- const [me, issue] = await Promise.all([
1994
- getAuthenticatedUser(config),
1995
- getTask(config, issueNumber)
1996
- ]);
1997
- if (issue.author && issue.author !== me) {
1998
- return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
2576
+ let issueNum = getConfig().taskState?.activeIssueNumber;
2577
+ if (!issueNum) {
2578
+ const found = await getIssueNumberFromBranch(config, branch);
2579
+ if (!found) return `No active task found (current branch: ${branch}).`;
2580
+ issueNum = found.issueNumber;
1999
2581
  }
2000
- const targetBranch = makeWorkerBranchName(me);
2001
- const spinner = ora8(`Merging PR for #${issueNumber}\u2026`).start();
2582
+ let issue;
2002
2583
  try {
2003
- const assigneeWorkerBranch = issue.assignee ? makeWorkerBranchName(issue.assignee) : void 0;
2004
- const result = await acceptTask(config, issueNumber, assigneeWorkerBranch);
2005
- spinner.stop();
2006
- return `Task #${issueNumber} accepted.
2007
- PR #${result.prNumber} merged \u2192 ${targetBranch}
2008
- Issue closed.`;
2584
+ issue = await getTask(config, issueNum);
2009
2585
  } catch (err) {
2010
- spinner.stop();
2011
2586
  return `Error: ${err.message}`;
2012
2587
  }
2588
+ await launchClaudeCode(issue, branch);
2589
+ return "Claude Code session ended.";
2013
2590
  }
2591
+ var execute10 = run10;
2014
2592
  var terminal10 = true;
2015
2593
 
2016
2594
  // src/tools/edit-task/index.ts
@@ -2022,7 +2600,7 @@ __export(edit_task_exports, {
2022
2600
  terminal: () => terminal11
2023
2601
  });
2024
2602
  init_github();
2025
- import { select as select7, input as promptInput4 } from "@inquirer/prompts";
2603
+ import { select as select8, input as promptInput4 } from "@inquirer/prompts";
2026
2604
  import ora9 from "ora";
2027
2605
  var definition11 = {
2028
2606
  type: "function",
@@ -2051,7 +2629,7 @@ async function run11(input, config) {
2051
2629
  }
2052
2630
  if (tasks.length === 0) return "No tasks found.";
2053
2631
  try {
2054
- issueNumber = await select7({
2632
+ issueNumber = await select8({
2055
2633
  message: "Select task to edit:",
2056
2634
  choices: tasks.map((t) => ({ name: `#${t.number} [${getStatus(t)}] ${t.title}`, value: t.number }))
2057
2635
  });
@@ -2108,14 +2686,264 @@ async function execute11(input, config) {
2108
2686
  }
2109
2687
  var terminal11 = true;
2110
2688
 
2689
+ // src/tools/move-task/index.ts
2690
+ var move_task_exports = {};
2691
+ __export(move_task_exports, {
2692
+ definition: () => definition12,
2693
+ execute: () => execute12,
2694
+ run: () => run12,
2695
+ terminal: () => terminal12
2696
+ });
2697
+ init_github();
2698
+ import { select as select9 } from "@inquirer/prompts";
2699
+ import ora10 from "ora";
2700
+ import chalk11 from "chalk";
2701
+ var definition12 = {
2702
+ type: "function",
2703
+ function: {
2704
+ name: "move_task",
2705
+ description: "Move one of your own published tasks to be a sub-task of another task. Updates the target branch and base commit so executors sync from the new parent HEAD. Equivalent to /move.",
2706
+ parameters: {
2707
+ type: "object",
2708
+ properties: {
2709
+ issue_number: { type: "number", description: "Issue number of the task to move." },
2710
+ parent_issue_number: { type: "number", description: "Issue number of the new parent task." }
2711
+ },
2712
+ required: ["issue_number", "parent_issue_number"]
2713
+ }
2714
+ }
2715
+ };
2716
+ async function run12(input, config) {
2717
+ const me = await getAuthenticatedUser(config);
2718
+ const allTasks = await listTasks(config);
2719
+ let issueNumber = input["issue_number"];
2720
+ let taskToMove;
2721
+ if (issueNumber) {
2722
+ try {
2723
+ taskToMove = await getTask(config, issueNumber);
2724
+ } catch (err) {
2725
+ return `Error loading task #${issueNumber}: ${err.message}`;
2726
+ }
2727
+ if (taskToMove.author !== me) {
2728
+ return `Task #${issueNumber} was not authored by you \u2014 you can only move your own tasks.`;
2729
+ }
2730
+ } else {
2731
+ const myTasks = allTasks.filter((t) => t.author === me);
2732
+ if (myTasks.length === 0) return "No tasks you authored are available to move.";
2733
+ try {
2734
+ issueNumber = await select9({
2735
+ message: "Select task to move:",
2736
+ choices: myTasks.map((t) => ({
2737
+ name: `#${t.number} [${getStatus(t)}] ${t.title}`,
2738
+ value: t.number
2739
+ }))
2740
+ });
2741
+ } catch {
2742
+ return "Cancelled.";
2743
+ }
2744
+ taskToMove = myTasks.find((t) => t.number === issueNumber);
2745
+ }
2746
+ const candidates = allTasks.filter((t) => t.number !== taskToMove.number);
2747
+ const resolveSpinner = ora10("Finding parent task branches\u2026").start();
2748
+ const parents = [];
2749
+ for (const t of candidates) {
2750
+ const branch = await getTaskBranch(config, t.number);
2751
+ if (branch) parents.push({ task: t, branch });
2752
+ }
2753
+ resolveSpinner.stop();
2754
+ if (parents.length === 0) {
2755
+ return "No other tasks with known branches are available as a parent.";
2756
+ }
2757
+ let parentIssueNumber = input["parent_issue_number"];
2758
+ let chosen;
2759
+ if (parentIssueNumber) {
2760
+ const found = parents.find((p) => p.task.number === parentIssueNumber);
2761
+ if (!found) {
2762
+ return `Task #${parentIssueNumber} is not available as a parent (no branch found or not open).`;
2763
+ }
2764
+ chosen = found;
2765
+ } else {
2766
+ try {
2767
+ const selectedBranch = await select9({
2768
+ message: `Move #${taskToMove.number} under which task?`,
2769
+ choices: parents.map((p) => ({
2770
+ name: `#${p.task.number} [${getStatus(p.task)}] ${p.task.title} ${chalk11.dim("\u2192 " + p.branch)}`,
2771
+ value: p.branch
2772
+ }))
2773
+ });
2774
+ chosen = parents.find((p) => p.branch === selectedBranch);
2775
+ } catch {
2776
+ return "Cancelled.";
2777
+ }
2778
+ }
2779
+ const sha = await getBranchHeadSha(config, chosen.branch);
2780
+ if (!sha) {
2781
+ return `Could not resolve HEAD of branch ${chosen.branch} \u2014 does it exist on the remote?`;
2782
+ }
2783
+ const spinner = ora10(`Moving #${taskToMove.number} under #${chosen.task.number}\u2026`).start();
2784
+ try {
2785
+ await moveTask(config, taskToMove.number, chosen.branch, sha);
2786
+ spinner.succeed(
2787
+ `Task #${taskToMove.number} moved under #${chosen.task.number} "${chosen.task.title}"
2788
+ target: ${chalk11.cyan(chosen.branch)} base: ${chalk11.dim(sha.slice(0, 7))}`
2789
+ );
2790
+ return `Task #${taskToMove.number} moved under #${chosen.task.number} (branch: ${chosen.branch}, base: ${sha.slice(0, 7)})`;
2791
+ } catch (err) {
2792
+ spinner.fail(`Failed: ${err.message}`);
2793
+ return `Error: ${err.message}`;
2794
+ }
2795
+ }
2796
+ async function execute12(input, config) {
2797
+ const me = await getAuthenticatedUser(config);
2798
+ const issueNumber = input["issue_number"];
2799
+ const parentIssueNumber = input["parent_issue_number"];
2800
+ const task = await getTask(config, issueNumber);
2801
+ if (task.author !== me) {
2802
+ return `Task #${issueNumber} was not authored by you \u2014 you can only move your own tasks.`;
2803
+ }
2804
+ const branch = await getTaskBranch(config, parentIssueNumber);
2805
+ if (!branch) return `No branch found for parent task #${parentIssueNumber}.`;
2806
+ const sha = await getBranchHeadSha(config, branch);
2807
+ if (!sha) return `Could not resolve HEAD of branch ${branch}.`;
2808
+ await moveTask(config, issueNumber, branch, sha);
2809
+ return `Task #${issueNumber} moved under #${parentIssueNumber} (branch: ${branch}, base: ${sha.slice(0, 7)})`;
2810
+ }
2811
+ var terminal12 = true;
2812
+
2813
+ // src/tools/wiki/index.ts
2814
+ var wiki_exports = {};
2815
+ __export(wiki_exports, {
2816
+ definition: () => definition13,
2817
+ execute: () => execute13,
2818
+ run: () => run13,
2819
+ terminal: () => terminal13
2820
+ });
2821
+ import ora11 from "ora";
2822
+ import chalk12 from "chalk";
2823
+ import { readFile as readFile2 } from "fs/promises";
2824
+ import { select as select10 } from "@inquirer/prompts";
2825
+ init_github();
2826
+ var WIKI_PATH = "TECHUNTER.md";
2827
+ var definition13 = {
2828
+ type: "function",
2829
+ function: {
2830
+ name: "update_wiki",
2831
+ description: "Generate or refresh the project overview document (TECHUNTER.md) by scanning the codebase. The document helps new team members understand what the project does, how it is architected, and how to start contributing. Equivalent to /wiki.",
2832
+ parameters: {
2833
+ type: "object",
2834
+ properties: {},
2835
+ required: []
2836
+ }
2837
+ }
2838
+ };
2839
+ async function readWikiContent(config) {
2840
+ try {
2841
+ return await readFile2(WIKI_PATH, "utf-8");
2842
+ } catch {
2843
+ }
2844
+ return getRepoFile(config, WIKI_PATH);
2845
+ }
2846
+ function printWiki(content) {
2847
+ const divider = chalk12.dim("\u2500".repeat(70));
2848
+ console.log("\n" + divider);
2849
+ console.log(chalk12.bold(" TECHUNTER.md"));
2850
+ console.log(divider);
2851
+ console.log(renderMarkdown(content));
2852
+ console.log(divider + "\n");
2853
+ }
2854
+ async function run13(_input, config) {
2855
+ const fetchSpinner = ora11("Checking for existing wiki\u2026").start();
2856
+ const existing = await readWikiContent(config).catch(() => null);
2857
+ fetchSpinner.stop();
2858
+ let action;
2859
+ try {
2860
+ action = await select10({
2861
+ message: "TECHUNTER.md \u2014 what would you like to do?",
2862
+ choices: [
2863
+ ...existing ? [{ name: "View current wiki", value: "view" }] : [],
2864
+ { name: existing ? "Regenerate & commit to repo" : "Generate & commit to repo", value: "generate" },
2865
+ { name: "Cancel", value: "cancel" }
2866
+ ]
2867
+ });
2868
+ } catch {
2869
+ return "Cancelled.";
2870
+ }
2871
+ if (action === "cancel") return "Cancelled.";
2872
+ if (action === "view") {
2873
+ printWiki(existing);
2874
+ return "Displayed TECHUNTER.md.";
2875
+ }
2876
+ const authSpinner = ora11("Checking permissions\u2026").start();
2877
+ let me;
2878
+ let allowed;
2879
+ try {
2880
+ me = await getAuthenticatedUser(config);
2881
+ allowed = await isCollaborator(config, me);
2882
+ authSpinner.stop();
2883
+ } catch (err) {
2884
+ authSpinner.stop();
2885
+ return `Error checking permissions: ${err.message}`;
2886
+ }
2887
+ if (!allowed) {
2888
+ return `Permission denied: only repository collaborators can update the project wiki.`;
2889
+ }
2890
+ const genSpinner = ora11("Analyzing project and generating overview\u2026").start();
2891
+ let content;
2892
+ try {
2893
+ content = await generateWiki(config);
2894
+ genSpinner.stop();
2895
+ } catch (err) {
2896
+ genSpinner.stop();
2897
+ return `Error generating wiki: ${err.message}`;
2898
+ }
2899
+ printWiki(content);
2900
+ let confirm;
2901
+ try {
2902
+ confirm = await select10({
2903
+ message: `Publish to repository as ${WIKI_PATH}?`,
2904
+ choices: [
2905
+ { name: "Yes, commit to repo", value: "publish" },
2906
+ { name: "Cancel", value: "cancel" }
2907
+ ]
2908
+ });
2909
+ } catch {
2910
+ return "Cancelled.";
2911
+ }
2912
+ if (confirm === "cancel") return "Cancelled.";
2913
+ const writeSpinner = ora11(`Writing ${WIKI_PATH}\u2026`).start();
2914
+ try {
2915
+ const url = await upsertRepoFile(config, WIKI_PATH, content, "docs: update TECHUNTER.md project overview");
2916
+ writeSpinner.succeed(`Written: ${url}`);
2917
+ console.log("");
2918
+ return `TECHUNTER.md updated \u2014 ${url}`;
2919
+ } catch (err) {
2920
+ writeSpinner.fail(`Failed: ${err.message}`);
2921
+ return `Error: ${err.message}`;
2922
+ }
2923
+ }
2924
+ async function execute13(_input, config) {
2925
+ const me = await getAuthenticatedUser(config);
2926
+ if (!await isCollaborator(config, me)) {
2927
+ return `Permission denied: only repository collaborators can update the project wiki.`;
2928
+ }
2929
+ const content = await generateWiki(config);
2930
+ try {
2931
+ const url = await upsertRepoFile(config, WIKI_PATH, content, "docs: update TECHUNTER.md project overview");
2932
+ return `TECHUNTER.md updated \u2014 ${url}`;
2933
+ } catch (err) {
2934
+ return `Error: ${err.message}`;
2935
+ }
2936
+ }
2937
+ var terminal13 = true;
2938
+
2111
2939
  // src/tools/list-tasks/index.ts
2112
2940
  var list_tasks_exports = {};
2113
2941
  __export(list_tasks_exports, {
2114
- definition: () => definition12,
2115
- execute: () => execute12
2942
+ definition: () => definition14,
2943
+ execute: () => execute14
2116
2944
  });
2117
2945
  init_github();
2118
- var definition12 = {
2946
+ var definition14 = {
2119
2947
  type: "function",
2120
2948
  function: {
2121
2949
  name: "list_tasks",
@@ -2127,7 +2955,7 @@ var definition12 = {
2127
2955
  }
2128
2956
  }
2129
2957
  };
2130
- async function execute12(_input, config) {
2958
+ async function execute14(_input, config) {
2131
2959
  const tasks = await listTasks(config);
2132
2960
  if (tasks.length === 0) return "No open tasks.";
2133
2961
  return tasks.map((t) => {
@@ -2140,11 +2968,11 @@ async function execute12(_input, config) {
2140
2968
  // src/tools/get-task/index.ts
2141
2969
  var get_task_exports = {};
2142
2970
  __export(get_task_exports, {
2143
- definition: () => definition13,
2144
- execute: () => execute13
2971
+ definition: () => definition15,
2972
+ execute: () => execute15
2145
2973
  });
2146
2974
  init_github();
2147
- var definition13 = {
2975
+ var definition15 = {
2148
2976
  type: "function",
2149
2977
  function: {
2150
2978
  name: "get_task",
@@ -2158,7 +2986,7 @@ var definition13 = {
2158
2986
  }
2159
2987
  }
2160
2988
  };
2161
- async function execute13(input, config) {
2989
+ async function execute15(input, config) {
2162
2990
  const issue = await getTask(config, input["issue_number"]);
2163
2991
  const status = issue.labels.find((l) => l.startsWith("techunter:"))?.replace("techunter:", "") ?? "unknown";
2164
2992
  const assignee = issue.assignee ? `@${issue.assignee}` : "\u2014";
@@ -2175,12 +3003,12 @@ ${issue.body}`);
2175
3003
  // src/tools/get-comments/index.ts
2176
3004
  var get_comments_exports = {};
2177
3005
  __export(get_comments_exports, {
2178
- definition: () => definition14,
2179
- execute: () => execute14
3006
+ definition: () => definition16,
3007
+ execute: () => execute16
2180
3008
  });
2181
3009
  init_github();
2182
- import ora10 from "ora";
2183
- var definition14 = {
3010
+ import ora12 from "ora";
3011
+ var definition16 = {
2184
3012
  type: "function",
2185
3013
  function: {
2186
3014
  name: "get_comments",
@@ -2195,10 +3023,10 @@ var definition14 = {
2195
3023
  }
2196
3024
  }
2197
3025
  };
2198
- async function execute14(input, config) {
3026
+ async function execute16(input, config) {
2199
3027
  const issueNumber = input["issue_number"];
2200
3028
  const limit = input["limit"] ?? 5;
2201
- const spinner = ora10(`Loading comments for #${issueNumber}...`).start();
3029
+ const spinner = ora12(`Loading comments for #${issueNumber}...`).start();
2202
3030
  try {
2203
3031
  const comments = await listComments(config, issueNumber, limit);
2204
3032
  spinner.stop();
@@ -2217,11 +3045,11 @@ ${lines.join("\n\n")}`;
2217
3045
  // src/tools/get-diff/index.ts
2218
3046
  var get_diff_exports = {};
2219
3047
  __export(get_diff_exports, {
2220
- definition: () => definition15,
2221
- execute: () => execute15
3048
+ definition: () => definition17,
3049
+ execute: () => execute17
2222
3050
  });
2223
- import ora11 from "ora";
2224
- var definition15 = {
3051
+ import ora13 from "ora";
3052
+ var definition17 = {
2225
3053
  type: "function",
2226
3054
  function: {
2227
3055
  name: "get_diff",
@@ -2229,8 +3057,8 @@ var definition15 = {
2229
3057
  parameters: { type: "object", properties: {}, required: [] }
2230
3058
  }
2231
3059
  };
2232
- async function execute15(_input, _config) {
2233
- const spinner = ora11("Reading git diff...").start();
3060
+ async function execute17(_input, _config) {
3061
+ const spinner = ora13("Reading git diff...").start();
2234
3062
  try {
2235
3063
  const diff = await getDiff();
2236
3064
  spinner.stop();
@@ -2244,14 +3072,14 @@ async function execute15(_input, _config) {
2244
3072
  // src/tools/run-command/index.ts
2245
3073
  var run_command_exports = {};
2246
3074
  __export(run_command_exports, {
2247
- definition: () => definition16,
2248
- execute: () => execute16
3075
+ definition: () => definition18,
3076
+ execute: () => execute18
2249
3077
  });
2250
3078
  import { exec } from "child_process";
2251
3079
  import { promisify } from "util";
2252
- import ora12 from "ora";
3080
+ import ora14 from "ora";
2253
3081
  var execAsync = promisify(exec);
2254
- var definition16 = {
3082
+ var definition18 = {
2255
3083
  type: "function",
2256
3084
  function: {
2257
3085
  name: "run_command",
@@ -2265,10 +3093,10 @@ var definition16 = {
2265
3093
  }
2266
3094
  }
2267
3095
  };
2268
- async function execute16(input, _config) {
3096
+ async function execute18(input, _config) {
2269
3097
  const command = input["command"];
2270
3098
  const cwd = process.cwd();
2271
- const spinner = ora12(`$ ${command}`).start();
3099
+ const spinner = ora14(`$ ${command}`).start();
2272
3100
  try {
2273
3101
  const { stdout, stderr } = await execAsync(command, { cwd, timeout: 6e4, maxBuffer: 1024 * 1024 });
2274
3102
  spinner.stop();
@@ -2287,15 +3115,15 @@ ${out || e.message}`;
2287
3115
  // src/tools/list-files/index.ts
2288
3116
  var list_files_exports = {};
2289
3117
  __export(list_files_exports, {
2290
- definition: () => definition17,
2291
- execute: () => execute17
3118
+ definition: () => definition19,
3119
+ execute: () => execute19
2292
3120
  });
2293
- import { readFile as readFile2 } from "fs/promises";
3121
+ import { readFile as readFile3 } from "fs/promises";
2294
3122
  import { existsSync } from "fs";
2295
3123
  import path2 from "path";
2296
3124
  import { globby } from "globby";
2297
3125
  import ignore from "ignore";
2298
- var definition17 = {
3126
+ var definition19 = {
2299
3127
  type: "function",
2300
3128
  function: {
2301
3129
  name: "list_files",
@@ -2335,13 +3163,13 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
2335
3163
  ".sqlite",
2336
3164
  ".lock"
2337
3165
  ]);
2338
- async function execute17(input, _config) {
3166
+ async function execute19(input, _config) {
2339
3167
  const glob = input["glob"] ?? "**/*";
2340
3168
  const cwd = process.cwd();
2341
3169
  const ig = ignore();
2342
3170
  const gitignorePath = path2.join(cwd, ".gitignore");
2343
3171
  if (existsSync(gitignorePath)) {
2344
- ig.add(await readFile2(gitignorePath, "utf-8"));
3172
+ ig.add(await readFile3(gitignorePath, "utf-8"));
2345
3173
  }
2346
3174
  ig.add(["node_modules", "dist", ".git", ".next", "__pycache__", "build", "coverage"]);
2347
3175
  const files = await globby(glob, { cwd, dot: false, onlyFiles: true, gitignore: false });
@@ -2354,15 +3182,15 @@ ${filtered.join("\n")}`;
2354
3182
  // src/tools/grep-code/index.ts
2355
3183
  var grep_code_exports = {};
2356
3184
  __export(grep_code_exports, {
2357
- definition: () => definition18,
2358
- execute: () => execute18
3185
+ definition: () => definition20,
3186
+ execute: () => execute20
2359
3187
  });
2360
- import { readFile as readFile3 } from "fs/promises";
3188
+ import { readFile as readFile4 } from "fs/promises";
2361
3189
  import { existsSync as existsSync2 } from "fs";
2362
3190
  import path3 from "path";
2363
3191
  import { globby as globby2 } from "globby";
2364
3192
  import ignore2 from "ignore";
2365
- var definition18 = {
3193
+ var definition20 = {
2366
3194
  type: "function",
2367
3195
  function: {
2368
3196
  name: "grep_code",
@@ -2403,7 +3231,7 @@ async function buildIgnore(cwd) {
2403
3231
  const ig = ignore2();
2404
3232
  const gitignorePath = path3.join(cwd, ".gitignore");
2405
3233
  if (existsSync2(gitignorePath)) {
2406
- ig.add(await readFile3(gitignorePath, "utf-8"));
3234
+ ig.add(await readFile4(gitignorePath, "utf-8"));
2407
3235
  }
2408
3236
  ig.add(["node_modules", "dist", ".git", ".next", "__pycache__", "build", "coverage"]);
2409
3237
  return ig;
@@ -2435,7 +3263,7 @@ function isText(f) {
2435
3263
  return !BINARY_EXTENSIONS2.has(path3.extname(f).toLowerCase());
2436
3264
  }
2437
3265
  var MAX_RANGE_LINES = 300;
2438
- async function execute18(input, _config) {
3266
+ async function execute20(input, _config) {
2439
3267
  const pattern = input["pattern"] ?? "";
2440
3268
  const fileGlob = input["file_glob"] ?? "**/*";
2441
3269
  const contextLines = Math.min(input["context_lines"] ?? 2, 5);
@@ -2447,7 +3275,7 @@ async function execute18(input, _config) {
2447
3275
  const files = await globby2(fileGlob, { cwd, dot: false, onlyFiles: true, gitignore: false });
2448
3276
  if (files.length === 0) return `No file matched: ${fileGlob}`;
2449
3277
  if (files.length > 1) return `file_glob matched ${files.length} files \u2014 narrow it to a single file for read-range mode.`;
2450
- const raw = await readFile3(path3.join(cwd, files[0]), "utf-8");
3278
+ const raw = await readFile4(path3.join(cwd, files[0]), "utf-8");
2451
3279
  const lines = raw.split("\n");
2452
3280
  const total = lines.length;
2453
3281
  const from = Math.max(1, startLine);
@@ -2476,7 +3304,7 @@ ${numbered}
2476
3304
  if (totalMatches >= maxResults) break;
2477
3305
  let content;
2478
3306
  try {
2479
- content = await readFile3(path3.join(cwd, file), "utf-8");
3307
+ content = await readFile4(path3.join(cwd, file), "utf-8");
2480
3308
  } catch {
2481
3309
  continue;
2482
3310
  }
@@ -2522,12 +3350,12 @@ ${snippets.join("\n---\n")}
2522
3350
  // src/tools/ask-user/index.ts
2523
3351
  var ask_user_exports = {};
2524
3352
  __export(ask_user_exports, {
2525
- definition: () => definition19,
2526
- execute: () => execute19
3353
+ definition: () => definition21,
3354
+ execute: () => execute21
2527
3355
  });
2528
- import chalk10 from "chalk";
2529
- import { select as select8, input as promptInput5 } from "@inquirer/prompts";
2530
- var definition19 = {
3356
+ import chalk13 from "chalk";
3357
+ import { select as select11, input as promptInput5 } from "@inquirer/prompts";
3358
+ var definition21 = {
2531
3359
  type: "function",
2532
3360
  function: {
2533
3361
  name: "ask_user",
@@ -2546,24 +3374,24 @@ var definition19 = {
2546
3374
  }
2547
3375
  }
2548
3376
  };
2549
- async function execute19(input, _config) {
3377
+ async function execute21(input, _config) {
2550
3378
  const question = input["question"];
2551
3379
  const options = input["options"];
2552
3380
  const OTHER = "__other__";
2553
3381
  console.log("");
2554
- console.log(chalk10.dim(" \u250C\u2500 Agent question " + "\u2500".repeat(51)));
2555
- console.log(chalk10.dim(" \u2502"));
3382
+ console.log(chalk13.dim(" \u250C\u2500 Agent question " + "\u2500".repeat(51)));
3383
+ console.log(chalk13.dim(" \u2502"));
2556
3384
  for (const line of question.split("\n")) {
2557
- console.log(chalk10.dim(" \u2502 ") + line);
3385
+ console.log(chalk13.dim(" \u2502 ") + line);
2558
3386
  }
2559
- console.log(chalk10.dim(" \u2514" + "\u2500".repeat(67)));
3387
+ console.log(chalk13.dim(" \u2514" + "\u2500".repeat(67)));
2560
3388
  let answer;
2561
3389
  try {
2562
- const chosen = await select8({
3390
+ const chosen = await select11({
2563
3391
  message: " ",
2564
3392
  choices: [
2565
3393
  ...options.map((o) => ({ name: o, value: o })),
2566
- { name: chalk10.dim("Other (describe below)"), value: OTHER }
3394
+ { name: chalk13.dim("Other (describe below)"), value: OTHER }
2567
3395
  ]
2568
3396
  });
2569
3397
  answer = chosen === OTHER ? await promptInput5({ message: "Your answer:" }) : chosen;
@@ -2588,6 +3416,8 @@ var toolModules = [
2588
3416
  reject_exports,
2589
3417
  accept_exports,
2590
3418
  edit_task_exports,
3419
+ move_task_exports,
3420
+ wiki_exports,
2591
3421
  // Low-level tools
2592
3422
  list_tasks_exports,
2593
3423
  get_task_exports,