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.
- package/README.md +135 -107
- package/dist/index.js +1960 -1003
- package/dist/mcp.js +1212 -382
- package/package.json +1 -1
package/dist/index.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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
owner,
|
|
238
|
-
|
|
239
|
-
|
|
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
|
|
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 =
|
|
368
|
-
if (
|
|
369
|
-
const { data:
|
|
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:
|
|
373
|
-
|
|
511
|
+
pull_number: prNumber,
|
|
512
|
+
mediaType: { format: "diff" }
|
|
374
513
|
});
|
|
375
|
-
|
|
376
|
-
|
|
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,19 +556,20 @@ 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
|
|
|
398
563
|
// src/index.ts
|
|
399
|
-
import
|
|
564
|
+
import chalk18 from "chalk";
|
|
400
565
|
import readline from "readline";
|
|
401
566
|
import { createRequire } from "module";
|
|
402
567
|
|
|
403
568
|
// src/commands/init.ts
|
|
404
|
-
import { input, password, select } from "@inquirer/prompts";
|
|
405
|
-
import
|
|
406
|
-
import
|
|
407
|
-
import
|
|
569
|
+
import { input, password, select as select12 } from "@inquirer/prompts";
|
|
570
|
+
import chalk14 from "chalk";
|
|
571
|
+
import ora15 from "ora";
|
|
572
|
+
import open2 from "open";
|
|
408
573
|
import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device";
|
|
409
574
|
|
|
410
575
|
// src/lib/config.ts
|
|
@@ -423,7 +588,8 @@ var configSchema = z.object({
|
|
|
423
588
|
}),
|
|
424
589
|
taskState: z.object({
|
|
425
590
|
activeIssueNumber: z.number().optional(),
|
|
426
|
-
baseCommit: z.string().optional()
|
|
591
|
+
baseCommit: z.string().optional(),
|
|
592
|
+
activeBranch: z.string().optional()
|
|
427
593
|
}).optional()
|
|
428
594
|
});
|
|
429
595
|
var store = new Conf({
|
|
@@ -510,14 +676,21 @@ function parseOwnerRepo(remoteUrl) {
|
|
|
510
676
|
}
|
|
511
677
|
return null;
|
|
512
678
|
}
|
|
513
|
-
function makeBranchName(issueNumber, username) {
|
|
514
|
-
const slug = username.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "user";
|
|
515
|
-
return `task-${issueNumber}-${slug}`;
|
|
516
|
-
}
|
|
517
679
|
function makeWorkerBranchName(username) {
|
|
518
680
|
const slug = username.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "user";
|
|
519
681
|
return `worker-${slug}`;
|
|
520
682
|
}
|
|
683
|
+
function makeTaskBranchName(issueNumber, username) {
|
|
684
|
+
const slug = username.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "user";
|
|
685
|
+
return `task-${issueNumber}-${slug}`;
|
|
686
|
+
}
|
|
687
|
+
function isTaskBranch(branch) {
|
|
688
|
+
return /^task-\d+-/.test(branch);
|
|
689
|
+
}
|
|
690
|
+
function parseIssueNumberFromBranch(branch) {
|
|
691
|
+
const match = branch.match(/^task-(\d+)-/);
|
|
692
|
+
return match ? parseInt(match[1], 10) : null;
|
|
693
|
+
}
|
|
521
694
|
async function getCurrentCommit() {
|
|
522
695
|
return (await git.revparse(["HEAD"])).trim();
|
|
523
696
|
}
|
|
@@ -638,16 +811,27 @@ async function getRemoteHeadSha(baseBranch) {
|
|
|
638
811
|
await git.fetch("origin", baseBranch);
|
|
639
812
|
return (await git.revparse([`origin/${baseBranch}`])).trim();
|
|
640
813
|
}
|
|
641
|
-
async function
|
|
642
|
-
const branches = await git.branch();
|
|
643
|
-
const
|
|
644
|
-
|
|
814
|
+
async function checkoutFromCommit(branchName, sha) {
|
|
815
|
+
const branches = await git.branch(["-a"]);
|
|
816
|
+
const exists = Object.keys(branches.branches).some(
|
|
817
|
+
(b) => b === branchName || b === `remotes/origin/${branchName}`
|
|
818
|
+
);
|
|
819
|
+
if (exists) {
|
|
645
820
|
await git.checkout(branchName);
|
|
646
|
-
await git.reset(["--hard", sha]);
|
|
647
821
|
} else {
|
|
648
822
|
await git.checkoutBranch(branchName, sha);
|
|
649
823
|
}
|
|
650
824
|
}
|
|
825
|
+
async function hasUncommittedChanges() {
|
|
826
|
+
const status = await git.status();
|
|
827
|
+
return !status.isClean();
|
|
828
|
+
}
|
|
829
|
+
async function stash(message) {
|
|
830
|
+
await git.stash(["push", "-u", "-m", message]);
|
|
831
|
+
}
|
|
832
|
+
async function stashPop() {
|
|
833
|
+
await git.stash(["pop"]);
|
|
834
|
+
}
|
|
651
835
|
|
|
652
836
|
// src/lib/client.ts
|
|
653
837
|
init_proxy();
|
|
@@ -665,330 +849,146 @@ function getModel(config) {
|
|
|
665
849
|
return config.aiModel ?? DEFAULT_MODEL;
|
|
666
850
|
}
|
|
667
851
|
|
|
668
|
-
// src/
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
852
|
+
// src/tools/pick/index.ts
|
|
853
|
+
var pick_exports = {};
|
|
854
|
+
__export(pick_exports, {
|
|
855
|
+
definition: () => definition3,
|
|
856
|
+
execute: () => execute3,
|
|
857
|
+
run: () => run3,
|
|
858
|
+
terminal: () => terminal3
|
|
859
|
+
});
|
|
860
|
+
init_github();
|
|
861
|
+
import chalk5 from "chalk";
|
|
862
|
+
import ora3 from "ora";
|
|
863
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
864
|
+
init_github();
|
|
865
|
+
|
|
866
|
+
// src/lib/markdown.ts
|
|
867
|
+
import { marked } from "marked";
|
|
868
|
+
import { markedTerminal } from "marked-terminal";
|
|
869
|
+
marked.use(markedTerminal({ showSectionPrefix: false }));
|
|
870
|
+
function renderMarkdown(text) {
|
|
871
|
+
return marked(text);
|
|
677
872
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
873
|
+
|
|
874
|
+
// src/lib/display.ts
|
|
875
|
+
init_github();
|
|
876
|
+
import chalk2 from "chalk";
|
|
877
|
+
var LABEL_AVAILABLE2 = "techunter:available";
|
|
878
|
+
var LABEL_CLAIMED2 = "techunter:claimed";
|
|
879
|
+
var LABEL_IN_REVIEW2 = "techunter:in-review";
|
|
880
|
+
var LABEL_CHANGES_NEEDED2 = "techunter:changes-needed";
|
|
881
|
+
function getStatus(issue) {
|
|
882
|
+
if (issue.labels.includes(LABEL_CHANGES_NEEDED2)) return "changes-needed";
|
|
883
|
+
if (issue.labels.includes(LABEL_IN_REVIEW2)) return "in-review";
|
|
884
|
+
if (issue.labels.includes(LABEL_CLAIMED2)) return "claimed";
|
|
885
|
+
if (issue.labels.includes(LABEL_AVAILABLE2)) return "available";
|
|
886
|
+
return "unknown";
|
|
887
|
+
}
|
|
888
|
+
function colorStatus(status) {
|
|
889
|
+
const padded = status.padEnd(14);
|
|
890
|
+
switch (status) {
|
|
891
|
+
case "available":
|
|
892
|
+
return chalk2.green(padded);
|
|
893
|
+
case "claimed":
|
|
894
|
+
return chalk2.yellow(padded);
|
|
895
|
+
case "in-review":
|
|
896
|
+
return chalk2.blue(padded);
|
|
897
|
+
case "changes-needed":
|
|
898
|
+
return chalk2.red(padded);
|
|
899
|
+
default:
|
|
900
|
+
return padded;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function parentIssueFromBranch(branch) {
|
|
904
|
+
if (!isTaskBranch(branch)) return null;
|
|
905
|
+
const match = branch.match(/^task-(\d+)-/);
|
|
906
|
+
return match ? parseInt(match[1], 10) : null;
|
|
907
|
+
}
|
|
908
|
+
function getParentIssueNumber(issue) {
|
|
909
|
+
const target = extractTargetBranch(issue.body);
|
|
910
|
+
if (!target) return null;
|
|
911
|
+
return parentIssueFromBranch(target);
|
|
912
|
+
}
|
|
913
|
+
function printTaskDetail(issue) {
|
|
914
|
+
const divider = chalk2.dim("\u2500".repeat(70));
|
|
915
|
+
const parentNum = getParentIssueNumber(issue);
|
|
916
|
+
console.log("\n" + divider);
|
|
917
|
+
console.log(
|
|
918
|
+
chalk2.bold(` #${issue.number}`) + " " + colorStatus(getStatus(issue)) + " " + chalk2.dim(issue.assignee ? `@${issue.assignee}` : "\u2014") + (parentNum ? chalk2.dim(` sub-task of #${parentNum}`) : "")
|
|
919
|
+
);
|
|
920
|
+
console.log(chalk2.bold("\n " + issue.title));
|
|
921
|
+
if (issue.body) {
|
|
922
|
+
console.log("");
|
|
923
|
+
console.log(renderMarkdown(issue.body));
|
|
924
|
+
}
|
|
925
|
+
console.log("\n " + chalk2.dim(issue.htmlUrl));
|
|
926
|
+
console.log(divider + "\n");
|
|
927
|
+
}
|
|
928
|
+
async function printTaskList(config) {
|
|
702
929
|
try {
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
930
|
+
const tasks = await listTasks(config);
|
|
931
|
+
const divider = chalk2.dim("\u2500".repeat(70));
|
|
932
|
+
console.log("");
|
|
933
|
+
console.log(chalk2.dim(" " + "#".padEnd(5) + "Status".padEnd(14) + "Assignee".padEnd(16) + "Title"));
|
|
934
|
+
console.log(divider);
|
|
935
|
+
if (tasks.length === 0) {
|
|
936
|
+
console.log(chalk2.dim(" (no tasks)"));
|
|
937
|
+
} else {
|
|
938
|
+
let printTask2 = function(t, indent, connector, isLast) {
|
|
939
|
+
const num = `#${t.number}`.padEnd(5);
|
|
940
|
+
const status = colorStatus(getStatus(t));
|
|
941
|
+
const assignee = (t.assignee ? `@${t.assignee}` : "\u2014").padEnd(16);
|
|
942
|
+
const fullPrefix = indent + connector;
|
|
943
|
+
const maxTitle = 36 - fullPrefix.length;
|
|
944
|
+
const title = t.title.length > maxTitle ? t.title.slice(0, maxTitle - 3) + "..." : t.title;
|
|
945
|
+
console.log(` ${num}${status}${assignee}${chalk2.dim(fullPrefix)}${title}`);
|
|
946
|
+
const children = childrenOf.get(t.number) ?? [];
|
|
947
|
+
const childIndent = indent + (isLast ? " " : "\u2502 ");
|
|
948
|
+
for (let i = 0; i < children.length; i++) {
|
|
949
|
+
const childIsLast = i === children.length - 1;
|
|
950
|
+
printTask2(children[i], childIndent, childIsLast ? "\u2514\u2500 " : "\u251C\u2500 ", childIsLast);
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
var printTask = printTask2;
|
|
954
|
+
const taskMap = new Map(tasks.map((t) => [t.number, t]));
|
|
955
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
956
|
+
for (const t of tasks) {
|
|
957
|
+
const parentNum = getParentIssueNumber(t);
|
|
958
|
+
const key = parentNum !== null && taskMap.has(parentNum) ? parentNum : null;
|
|
959
|
+
if (!childrenOf.has(key)) childrenOf.set(key, []);
|
|
960
|
+
childrenOf.get(key).push(t);
|
|
961
|
+
}
|
|
962
|
+
const roots = childrenOf.get(null) ?? [];
|
|
963
|
+
for (let i = 0; i < roots.length; i++) {
|
|
964
|
+
const isLast = i === roots.length - 1;
|
|
965
|
+
printTask2(roots[i], "", isLast ? "\u2514\u2500 " : "\u251C\u2500 ", isLast);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
console.log(divider);
|
|
969
|
+
return tasks;
|
|
706
970
|
} catch (err) {
|
|
707
|
-
|
|
708
|
-
|
|
971
|
+
console.log(chalk2.yellow(`(Could not load tasks: ${err.message})`));
|
|
972
|
+
return [];
|
|
709
973
|
}
|
|
710
|
-
return { token, clientId: OAUTH_CLIENT_ID };
|
|
711
974
|
}
|
|
712
|
-
async function
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
{
|
|
730
|
-
name: "Browser login (OAuth) \u2014 open a URL and click Authorize",
|
|
731
|
-
value: "device"
|
|
732
|
-
},
|
|
733
|
-
{
|
|
734
|
-
name: "Personal Access Token (PAT) \u2014 paste a token from github.com/settings/tokens",
|
|
735
|
-
value: "pat"
|
|
736
|
-
}
|
|
737
|
-
]
|
|
738
|
-
});
|
|
739
|
-
let githubToken;
|
|
740
|
-
let githubClientId;
|
|
741
|
-
if (authMethod === "device") {
|
|
742
|
-
const result = await getGitHubTokenViaDeviceFlow();
|
|
743
|
-
githubToken = result.token;
|
|
744
|
-
githubClientId = result.clientId;
|
|
745
|
-
} else {
|
|
746
|
-
const result = await getGitHubTokenViaPAT();
|
|
747
|
-
githubToken = result.token;
|
|
748
|
-
}
|
|
749
|
-
const providerChoice = await select({
|
|
750
|
-
message: "AI provider:",
|
|
751
|
-
choices: [
|
|
752
|
-
{ name: `OpenRouter (recommended) ${chalk2.dim(`${DEFAULT_BASE_URL} \xB7 ${DEFAULT_MODEL}`)}`, value: "openrouter" },
|
|
753
|
-
{ name: "Custom (specify base URL and model)", value: "custom" }
|
|
754
|
-
]
|
|
755
|
-
});
|
|
756
|
-
let aiBaseUrl;
|
|
757
|
-
let aiModel;
|
|
758
|
-
if (providerChoice === "custom") {
|
|
759
|
-
aiBaseUrl = (await input({ message: "API base URL:", default: DEFAULT_BASE_URL })).trim();
|
|
760
|
-
aiModel = (await input({ message: "Model name:", default: DEFAULT_MODEL })).trim();
|
|
761
|
-
}
|
|
762
|
-
const apiKeyHint = providerChoice === "openrouter" ? chalk2.dim(" Get a key at: https://openrouter.ai/settings/keys\n") : chalk2.dim(" API key for your provider\n");
|
|
763
|
-
console.log(apiKeyHint);
|
|
764
|
-
const aiApiKey = await password({
|
|
765
|
-
message: "API Key:",
|
|
766
|
-
mask: "*"
|
|
767
|
-
});
|
|
768
|
-
let owner = detectedOwner;
|
|
769
|
-
let repo = detectedRepo;
|
|
770
|
-
if (!owner || !repo) {
|
|
771
|
-
owner = await input({
|
|
772
|
-
message: "GitHub repo owner (user or org):",
|
|
773
|
-
required: true
|
|
774
|
-
});
|
|
775
|
-
repo = await input({
|
|
776
|
-
message: "GitHub repo name:",
|
|
777
|
-
required: true
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
const config = {
|
|
781
|
-
githubToken,
|
|
782
|
-
githubClientId,
|
|
783
|
-
aiApiKey: aiApiKey.trim(),
|
|
784
|
-
...aiBaseUrl ? { aiBaseUrl } : {},
|
|
785
|
-
...aiModel ? { aiModel } : {},
|
|
786
|
-
github: {
|
|
787
|
-
owner: owner.trim(),
|
|
788
|
-
repo: repo.trim()
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
setConfig(config);
|
|
792
|
-
const spinner = ora("Setting up GitHub labels...").start();
|
|
793
|
-
try {
|
|
794
|
-
await ensureLabels(config);
|
|
795
|
-
spinner.succeed("GitHub labels created");
|
|
796
|
-
} catch (err) {
|
|
797
|
-
spinner.fail("Failed to create labels (check token permissions)");
|
|
798
|
-
console.error(chalk2.red(String(err)));
|
|
799
|
-
}
|
|
800
|
-
console.log(chalk2.green("\nSetup complete!"));
|
|
801
|
-
console.log(chalk2.dim(`Config saved to: ${getConfigPath()}
|
|
802
|
-
`));
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// src/commands/config.ts
|
|
806
|
-
import { input as input2, password as password2, select as select2 } from "@inquirer/prompts";
|
|
807
|
-
import chalk3 from "chalk";
|
|
808
|
-
async function configCommand() {
|
|
809
|
-
let config;
|
|
810
|
-
try {
|
|
811
|
-
config = getConfig();
|
|
812
|
-
} catch {
|
|
813
|
-
console.error(chalk3.red("No config found. Run `tch init` first."));
|
|
814
|
-
process.exit(1);
|
|
815
|
-
}
|
|
816
|
-
console.log(chalk3.bold.cyan("\nTechunter \u2014 Settings\n"));
|
|
817
|
-
console.log(chalk3.dim(`Config file: ${getConfigPath()}
|
|
818
|
-
`));
|
|
819
|
-
const currentBaseUrl = config.aiBaseUrl ?? DEFAULT_BASE_URL;
|
|
820
|
-
const currentModel = config.aiModel ?? DEFAULT_MODEL;
|
|
821
|
-
const currentBaseBranch = config.baseBranch ?? "main";
|
|
822
|
-
const field = await select2({
|
|
823
|
-
message: "Which setting to change?",
|
|
824
|
-
choices: [
|
|
825
|
-
{ name: `GitHub repo ${chalk3.dim(`${config.github.owner}/${config.github.repo}`)}`, value: "repo" },
|
|
826
|
-
{ name: `Base branch ${chalk3.dim(currentBaseBranch)}`, value: "baseBranch" },
|
|
827
|
-
{ name: `AI base URL ${chalk3.dim(currentBaseUrl)}`, value: "aiBaseUrl" },
|
|
828
|
-
{ name: `AI model ${chalk3.dim(currentModel)}`, value: "aiModel" },
|
|
829
|
-
{ name: `AI API Key ${chalk3.dim("(hidden)")}`, value: "aiApiKey" },
|
|
830
|
-
{ name: `GitHub Token ${chalk3.dim("(hidden)")}`, value: "githubToken" },
|
|
831
|
-
{ name: "Cancel", value: "cancel" }
|
|
832
|
-
]
|
|
833
|
-
});
|
|
834
|
-
if (field === "cancel") return;
|
|
835
|
-
if (field === "baseBranch") {
|
|
836
|
-
const val = await input2({ message: "Base branch name:", default: currentBaseBranch });
|
|
837
|
-
if (val.trim()) {
|
|
838
|
-
setConfig({ baseBranch: val.trim() });
|
|
839
|
-
console.log(chalk3.green(`
|
|
840
|
-
Base branch set to: ${val.trim()}
|
|
841
|
-
`));
|
|
842
|
-
}
|
|
843
|
-
} else if (field === "repo") {
|
|
844
|
-
const owner = await input2({ message: "GitHub repo owner:", default: config.github.owner });
|
|
845
|
-
const repo = await input2({ message: "GitHub repo name:", default: config.github.repo });
|
|
846
|
-
setConfig({ github: { ...config.github, owner: owner.trim(), repo: repo.trim() } });
|
|
847
|
-
console.log(chalk3.green(`
|
|
848
|
-
Repo set to: ${owner.trim()}/${repo.trim()}
|
|
849
|
-
`));
|
|
850
|
-
} else if (field === "aiBaseUrl") {
|
|
851
|
-
const val = await input2({ message: "AI base URL:", default: currentBaseUrl });
|
|
852
|
-
if (val.trim()) {
|
|
853
|
-
setConfig({ aiBaseUrl: val.trim() });
|
|
854
|
-
console.log(chalk3.green(`
|
|
855
|
-
AI base URL set to: ${val.trim()}
|
|
856
|
-
`));
|
|
857
|
-
}
|
|
858
|
-
} else if (field === "aiModel") {
|
|
859
|
-
const val = await input2({ message: "AI model name:", default: currentModel });
|
|
860
|
-
if (val.trim()) {
|
|
861
|
-
setConfig({ aiModel: val.trim() });
|
|
862
|
-
console.log(chalk3.green(`
|
|
863
|
-
AI model set to: ${val.trim()}
|
|
864
|
-
`));
|
|
865
|
-
}
|
|
866
|
-
} else if (field === "aiApiKey") {
|
|
867
|
-
const val = await password2({ message: "New AI API Key:", mask: "*" });
|
|
868
|
-
if (val.trim()) {
|
|
869
|
-
setConfig({ aiApiKey: val.trim() });
|
|
870
|
-
console.log(chalk3.green("\nAI API Key updated.\n"));
|
|
871
|
-
}
|
|
872
|
-
} else if (field === "githubToken") {
|
|
873
|
-
const val = await password2({ message: "New GitHub Token:", mask: "*" });
|
|
874
|
-
if (val.trim()) {
|
|
875
|
-
setConfig({ githubToken: val.trim() });
|
|
876
|
-
console.log(chalk3.green("\nGitHub Token updated.\n"));
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
// src/index.ts
|
|
882
|
-
init_github();
|
|
883
|
-
|
|
884
|
-
// src/lib/agent.ts
|
|
885
|
-
import ora14 from "ora";
|
|
886
|
-
import chalk13 from "chalk";
|
|
887
|
-
|
|
888
|
-
// src/tools/pick/index.ts
|
|
889
|
-
var pick_exports = {};
|
|
890
|
-
__export(pick_exports, {
|
|
891
|
-
definition: () => definition3,
|
|
892
|
-
execute: () => execute3,
|
|
893
|
-
run: () => run3,
|
|
894
|
-
terminal: () => terminal3
|
|
895
|
-
});
|
|
896
|
-
init_github();
|
|
897
|
-
import chalk8 from "chalk";
|
|
898
|
-
import ora4 from "ora";
|
|
899
|
-
import { select as select5 } from "@inquirer/prompts";
|
|
900
|
-
init_github();
|
|
901
|
-
|
|
902
|
-
// src/lib/markdown.ts
|
|
903
|
-
import { marked } from "marked";
|
|
904
|
-
import { markedTerminal } from "marked-terminal";
|
|
905
|
-
marked.use(markedTerminal({ showSectionPrefix: false }));
|
|
906
|
-
function renderMarkdown(text) {
|
|
907
|
-
return marked(text);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// src/lib/display.ts
|
|
911
|
-
init_github();
|
|
912
|
-
import chalk4 from "chalk";
|
|
913
|
-
var LABEL_AVAILABLE2 = "techunter:available";
|
|
914
|
-
var LABEL_CLAIMED2 = "techunter:claimed";
|
|
915
|
-
var LABEL_IN_REVIEW2 = "techunter:in-review";
|
|
916
|
-
var LABEL_CHANGES_NEEDED2 = "techunter:changes-needed";
|
|
917
|
-
function getStatus(issue) {
|
|
918
|
-
if (issue.labels.includes(LABEL_CHANGES_NEEDED2)) return "changes-needed";
|
|
919
|
-
if (issue.labels.includes(LABEL_IN_REVIEW2)) return "in-review";
|
|
920
|
-
if (issue.labels.includes(LABEL_CLAIMED2)) return "claimed";
|
|
921
|
-
if (issue.labels.includes(LABEL_AVAILABLE2)) return "available";
|
|
922
|
-
return "unknown";
|
|
923
|
-
}
|
|
924
|
-
function colorStatus(status) {
|
|
925
|
-
const padded = status.padEnd(14);
|
|
926
|
-
switch (status) {
|
|
927
|
-
case "available":
|
|
928
|
-
return chalk4.green(padded);
|
|
929
|
-
case "claimed":
|
|
930
|
-
return chalk4.yellow(padded);
|
|
931
|
-
case "in-review":
|
|
932
|
-
return chalk4.blue(padded);
|
|
933
|
-
case "changes-needed":
|
|
934
|
-
return chalk4.red(padded);
|
|
935
|
-
default:
|
|
936
|
-
return padded;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
function printTaskDetail(issue) {
|
|
940
|
-
const divider = chalk4.dim("\u2500".repeat(70));
|
|
941
|
-
console.log("\n" + divider);
|
|
942
|
-
console.log(
|
|
943
|
-
chalk4.bold(` #${issue.number}`) + " " + colorStatus(getStatus(issue)) + " " + chalk4.dim(issue.assignee ? `@${issue.assignee}` : "\u2014")
|
|
944
|
-
);
|
|
945
|
-
console.log(chalk4.bold("\n " + issue.title));
|
|
946
|
-
if (issue.body) {
|
|
947
|
-
console.log("");
|
|
948
|
-
console.log(renderMarkdown(issue.body));
|
|
949
|
-
}
|
|
950
|
-
console.log("\n " + chalk4.dim(issue.htmlUrl));
|
|
951
|
-
console.log(divider + "\n");
|
|
952
|
-
}
|
|
953
|
-
async function printTaskList(config) {
|
|
954
|
-
try {
|
|
955
|
-
const tasks = await listTasks(config);
|
|
956
|
-
const divider = chalk4.dim("\u2500".repeat(70));
|
|
957
|
-
console.log("");
|
|
958
|
-
console.log(chalk4.dim(" " + "#".padEnd(5) + "Status".padEnd(14) + "Assignee".padEnd(16) + "Title"));
|
|
959
|
-
console.log(divider);
|
|
960
|
-
if (tasks.length === 0) {
|
|
961
|
-
console.log(chalk4.dim(" (no tasks)"));
|
|
962
|
-
} else {
|
|
963
|
-
for (const t of tasks) {
|
|
964
|
-
const num = `#${t.number}`.padEnd(5);
|
|
965
|
-
const status = colorStatus(getStatus(t));
|
|
966
|
-
const assignee = (t.assignee ? `@${t.assignee}` : "\u2014").padEnd(16);
|
|
967
|
-
const title = t.title.length > 36 ? t.title.slice(0, 33) + "..." : t.title;
|
|
968
|
-
console.log(` ${num}${status}${assignee}${title}`);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
console.log(divider);
|
|
972
|
-
return tasks;
|
|
973
|
-
} catch (err) {
|
|
974
|
-
console.log(chalk4.yellow(`(Could not load tasks: ${err.message})`));
|
|
975
|
-
return [];
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
async function printMyTasks(config) {
|
|
979
|
-
try {
|
|
980
|
-
const me = await getAuthenticatedUser(config);
|
|
981
|
-
const tasks = await listMyTasks(config, me);
|
|
982
|
-
if (tasks.length === 0) return;
|
|
983
|
-
const divider = chalk4.dim("\u2500".repeat(70));
|
|
984
|
-
console.log("");
|
|
985
|
-
console.log(chalk4.dim(" " + "#".padEnd(5) + "Status".padEnd(14) + `My Tasks @${me}`));
|
|
986
|
-
console.log(divider);
|
|
987
|
-
for (const t of tasks) {
|
|
988
|
-
const num = `#${t.number}`.padEnd(5);
|
|
989
|
-
const status = colorStatus(getStatus(t));
|
|
990
|
-
const title = t.title.length > 46 ? t.title.slice(0, 43) + "..." : t.title;
|
|
991
|
-
console.log(` ${num}${status}${title}`);
|
|
975
|
+
async function printMyTasks(config) {
|
|
976
|
+
try {
|
|
977
|
+
const me = await getAuthenticatedUser(config);
|
|
978
|
+
const tasks = await listMyTasks(config, me);
|
|
979
|
+
if (tasks.length === 0) return;
|
|
980
|
+
const divider = chalk2.dim("\u2500".repeat(70));
|
|
981
|
+
console.log("");
|
|
982
|
+
console.log(chalk2.dim(" " + "#".padEnd(5) + "Status".padEnd(14) + `My Tasks @${me}`));
|
|
983
|
+
console.log(divider);
|
|
984
|
+
for (const t of tasks) {
|
|
985
|
+
const num = `#${t.number}`.padEnd(5);
|
|
986
|
+
const status = colorStatus(getStatus(t));
|
|
987
|
+
const parentNum = getParentIssueNumber(t);
|
|
988
|
+
const parentTag = parentNum ? chalk2.dim(` (sub of #${parentNum})`) : "";
|
|
989
|
+
const maxTitle = parentNum ? 34 : 46;
|
|
990
|
+
const title = t.title.length > maxTitle ? t.title.slice(0, maxTitle - 3) + "..." : t.title;
|
|
991
|
+
console.log(` ${num}${status}${title}${parentTag}`);
|
|
992
992
|
}
|
|
993
993
|
console.log(divider);
|
|
994
994
|
const rejectedTasks = tasks.filter((t) => t.labels.includes(LABEL_CHANGES_NEEDED2));
|
|
@@ -1000,14 +1000,14 @@ async function printMyTasks(config) {
|
|
|
1000
1000
|
}
|
|
1001
1001
|
console.log("");
|
|
1002
1002
|
for (const t of rejectedTasks) {
|
|
1003
|
-
const
|
|
1004
|
-
const onCorrectBranch = currentBranch ===
|
|
1003
|
+
const taskBranch = t.assignee ? makeTaskBranchName(t.number, t.assignee) : `task-${t.number}`;
|
|
1004
|
+
const onCorrectBranch = currentBranch === taskBranch;
|
|
1005
1005
|
console.log(
|
|
1006
|
-
|
|
1006
|
+
chalk2.red.bold(" \u26A0 Changes requested") + chalk2.red(` on #${t.number} "${t.title}"`)
|
|
1007
1007
|
);
|
|
1008
1008
|
if (!onCorrectBranch) {
|
|
1009
1009
|
console.log(
|
|
1010
|
-
|
|
1010
|
+
chalk2.dim(" Switch branch: ") + chalk2.cyan(`git checkout ${taskBranch}`)
|
|
1011
1011
|
);
|
|
1012
1012
|
}
|
|
1013
1013
|
}
|
|
@@ -1019,7 +1019,7 @@ async function printMyTasks(config) {
|
|
|
1019
1019
|
|
|
1020
1020
|
// src/lib/launch.ts
|
|
1021
1021
|
import { spawn } from "child_process";
|
|
1022
|
-
import
|
|
1022
|
+
import chalk3 from "chalk";
|
|
1023
1023
|
function buildClaudePrompt(issue, branch) {
|
|
1024
1024
|
const lines = [
|
|
1025
1025
|
`You are working on task #${issue.number}: ${issue.title}`,
|
|
@@ -1035,14 +1035,14 @@ function buildClaudePrompt(issue, branch) {
|
|
|
1035
1035
|
}
|
|
1036
1036
|
async function launchClaudeCode(issue, branch) {
|
|
1037
1037
|
const prompt = buildClaudePrompt(issue, branch);
|
|
1038
|
-
console.log(
|
|
1038
|
+
console.log(chalk3.dim("\n Launching Claude Code\u2026\n"));
|
|
1039
1039
|
await new Promise((resolve) => {
|
|
1040
1040
|
const safePrompt = prompt.replace(/\r?\n/g, " ").replace(/"/g, "'");
|
|
1041
1041
|
const child = spawn(`claude "${safePrompt}"`, [], { stdio: "inherit", shell: true });
|
|
1042
1042
|
child.on("close", () => resolve());
|
|
1043
1043
|
child.on("error", () => {
|
|
1044
1044
|
console.log(
|
|
1045
|
-
|
|
1045
|
+
chalk3.yellow(
|
|
1046
1046
|
" Could not launch claude. Make sure Claude Code is installed:\n npm install -g @anthropic-ai/claude-code"
|
|
1047
1047
|
)
|
|
1048
1048
|
);
|
|
@@ -1060,81 +1060,12 @@ __export(submit_exports, {
|
|
|
1060
1060
|
terminal: () => terminal
|
|
1061
1061
|
});
|
|
1062
1062
|
init_github();
|
|
1063
|
-
import
|
|
1064
|
-
import
|
|
1065
|
-
import { select
|
|
1063
|
+
import chalk4 from "chalk";
|
|
1064
|
+
import ora from "ora";
|
|
1065
|
+
import { select, input as promptInput } from "@inquirer/prompts";
|
|
1066
1066
|
|
|
1067
|
-
// src/
|
|
1068
|
-
|
|
1069
|
-
function formatInput(input3) {
|
|
1070
|
-
return Object.entries(input3).map(([k, v]) => {
|
|
1071
|
-
if (typeof v === "number") return `${k}=${v}`;
|
|
1072
|
-
if (typeof v === "string") {
|
|
1073
|
-
if (k === "body" || v.length > 50) return `${k}=[${v.length} chars]`;
|
|
1074
|
-
return `${k}="${v}"`;
|
|
1075
|
-
}
|
|
1076
|
-
return `${k}=${JSON.stringify(v)}`;
|
|
1077
|
-
}).join(" ");
|
|
1078
|
-
}
|
|
1079
|
-
function summarize(result) {
|
|
1080
|
-
const first = result.split("\n").find((l) => l.trim()) ?? result;
|
|
1081
|
-
return first.length > 100 ? first.slice(0, 97) + "..." : first;
|
|
1082
|
-
}
|
|
1083
|
-
function printToolCall(name, input3) {
|
|
1084
|
-
const params = formatInput(input3);
|
|
1085
|
-
console.log(` ${chalk6.cyan("\u2192")} ${chalk6.bold(name)}${params ? " " + chalk6.dim(params) : ""}`);
|
|
1086
|
-
}
|
|
1087
|
-
function printToolResult(result) {
|
|
1088
|
-
const ok = !result.startsWith("Error:");
|
|
1089
|
-
const icon = ok ? chalk6.green("\u2713") : chalk6.red("\u2717");
|
|
1090
|
-
console.log(` ${icon} ${chalk6.dim(summarize(result))}`);
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// src/lib/sub-agent.ts
|
|
1094
|
-
async function runSubAgentLoop(config, systemPrompt, userMessage, toolNames) {
|
|
1095
|
-
const client = createClient(config);
|
|
1096
|
-
const selected = toolModules.filter((m) => toolNames.includes(m.definition.function.name));
|
|
1097
|
-
const tools2 = selected.map((m) => m.definition);
|
|
1098
|
-
const messages = [
|
|
1099
|
-
{ role: "system", content: systemPrompt },
|
|
1100
|
-
{ role: "user", content: userMessage }
|
|
1101
|
-
];
|
|
1102
|
-
const MAX_ITERATIONS = 100;
|
|
1103
|
-
let iterations = 0;
|
|
1104
|
-
for (; ; ) {
|
|
1105
|
-
if (++iterations > MAX_ITERATIONS) {
|
|
1106
|
-
throw new Error(`Sub-agent exceeded ${MAX_ITERATIONS} iterations without finishing.`);
|
|
1107
|
-
}
|
|
1108
|
-
const res = await client.chat.completions.create({ model: getModel(config), tools: tools2, messages });
|
|
1109
|
-
const choice = res.choices[0];
|
|
1110
|
-
messages.push({
|
|
1111
|
-
role: "assistant",
|
|
1112
|
-
content: choice.message.content ?? null,
|
|
1113
|
-
...choice.message.tool_calls ? { tool_calls: choice.message.tool_calls } : {}
|
|
1114
|
-
});
|
|
1115
|
-
if (choice.finish_reason === "stop") {
|
|
1116
|
-
return choice.message.content ?? "";
|
|
1117
|
-
}
|
|
1118
|
-
if (choice.finish_reason === "tool_calls") {
|
|
1119
|
-
for (const tc of choice.message.tool_calls ?? []) {
|
|
1120
|
-
let input3;
|
|
1121
|
-
try {
|
|
1122
|
-
input3 = JSON.parse(tc.function.arguments);
|
|
1123
|
-
} catch {
|
|
1124
|
-
input3 = {};
|
|
1125
|
-
}
|
|
1126
|
-
printToolCall(tc.function.name, input3);
|
|
1127
|
-
const mod = selected.find((m) => m.definition.function.name === tc.function.name);
|
|
1128
|
-
const result = mod ? await mod.execute(input3, config) : `Unknown tool: ${tc.function.name}`;
|
|
1129
|
-
printToolResult(result);
|
|
1130
|
-
messages.push({ role: "tool", tool_call_id: tc.id, content: result });
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
// src/tools/submit/prompts.ts
|
|
1137
|
-
var REVIEWER_SYSTEM_PROMPT = "You are a concise code reviewer. The diff provided shows all changes on this worker branch since the task was claimed. The branch is shared across tasks, so the diff may contain changes unrelated to this specific task \u2014 use the task title and acceptance criteria to identify which changes are relevant, and ignore the rest. Use run_command to run tests/lint if needed, and read_file to inspect specific files. Then output your review: for each acceptance criterion mark \u2705 met or \u274C not met with a one-line reason. End with an overall verdict line: Ready to submit / Not ready. Reply in the same language as the task.";
|
|
1067
|
+
// src/tools/submit/prompts.ts
|
|
1068
|
+
var REVIEWER_SYSTEM_PROMPT = "You are a concise code reviewer. The diff provided shows all changes on this worker branch since the task was claimed. The branch is shared across tasks, so the diff may contain changes unrelated to this specific task \u2014 use the task title and acceptance criteria to identify which changes are relevant, and ignore the rest. Use run_command to run tests/lint if needed, and read_file to inspect specific files. Then output your review: for each acceptance criterion mark \u2705 met or \u274C not met with a one-line reason. End with an overall verdict line: Ready to submit / Not ready. Reply in the same language as the task.";
|
|
1138
1069
|
|
|
1139
1070
|
// src/tools/submit/reviewer.ts
|
|
1140
1071
|
async function reviewChanges(config, issueNumber, issue, diff) {
|
|
@@ -1169,11 +1100,21 @@ var definition = {
|
|
|
1169
1100
|
};
|
|
1170
1101
|
async function run(_input, config) {
|
|
1171
1102
|
const taskState = getConfig().taskState;
|
|
1172
|
-
const
|
|
1103
|
+
const currentBranch = await getCurrentBranch();
|
|
1104
|
+
let issueNumber = taskState?.activeIssueNumber && taskState?.activeBranch && currentBranch === taskState.activeBranch ? taskState.activeIssueNumber : void 0;
|
|
1173
1105
|
if (!issueNumber) {
|
|
1174
|
-
|
|
1106
|
+
const fromBranch = parseIssueNumberFromBranch(currentBranch);
|
|
1107
|
+
if (fromBranch) {
|
|
1108
|
+
issueNumber = fromBranch;
|
|
1109
|
+
} else {
|
|
1110
|
+
const found = await getIssueNumberFromBranch(config, currentBranch);
|
|
1111
|
+
if (!found) {
|
|
1112
|
+
return "No active task found. Claim a task first with /pick.";
|
|
1113
|
+
}
|
|
1114
|
+
issueNumber = found.issueNumber;
|
|
1115
|
+
}
|
|
1175
1116
|
}
|
|
1176
|
-
let spinner =
|
|
1117
|
+
let spinner = ora("Loading task and diff\u2026").start();
|
|
1177
1118
|
const diffPromise = taskState?.baseCommit ? getDiffFromCommit(taskState.baseCommit) : getDiff();
|
|
1178
1119
|
const [issue, diff, me] = await Promise.all([
|
|
1179
1120
|
getTask(config, issueNumber),
|
|
@@ -1181,12 +1122,19 @@ async function run(_input, config) {
|
|
|
1181
1122
|
getAuthenticatedUser(config)
|
|
1182
1123
|
]);
|
|
1183
1124
|
spinner.stop();
|
|
1184
|
-
const
|
|
1125
|
+
const targetBranch = extractTargetBranch(issue.body) ?? makeWorkerBranchName(issue.author ?? me);
|
|
1185
1126
|
const branch = await getCurrentBranch();
|
|
1186
1127
|
const isSelfSubmit = issue.author !== null && issue.author === me;
|
|
1128
|
+
spinner = ora("Checking for open sub-tasks\u2026").start();
|
|
1129
|
+
const openSubtaskNumbers = await getOpenSubtasks(config, branch);
|
|
1130
|
+
spinner.stop();
|
|
1131
|
+
if (openSubtaskNumbers.length > 0) {
|
|
1132
|
+
return `Cannot submit: ${openSubtaskNumbers.length} sub-task(s) still open:
|
|
1133
|
+
` + openSubtaskNumbers.map((n) => ` - #${n}`).join("\n") + "\nComplete all sub-tasks before submitting.";
|
|
1134
|
+
}
|
|
1187
1135
|
let review = "";
|
|
1188
1136
|
if (!isSelfSubmit) {
|
|
1189
|
-
const reviewSpinner =
|
|
1137
|
+
const reviewSpinner = ora("Reviewing changes\u2026").start();
|
|
1190
1138
|
try {
|
|
1191
1139
|
review = await reviewChanges(config, issueNumber, issue, diff);
|
|
1192
1140
|
} catch (err) {
|
|
@@ -1194,19 +1142,19 @@ async function run(_input, config) {
|
|
|
1194
1142
|
}
|
|
1195
1143
|
reviewSpinner.stop();
|
|
1196
1144
|
}
|
|
1197
|
-
const divider =
|
|
1145
|
+
const divider = chalk4.dim("\u2500".repeat(70));
|
|
1198
1146
|
console.log("\n" + divider);
|
|
1199
1147
|
if (isSelfSubmit) {
|
|
1200
|
-
console.log(
|
|
1148
|
+
console.log(chalk4.yellow(` Self-submit detected \u2014 AI review skipped.`));
|
|
1201
1149
|
} else {
|
|
1202
|
-
console.log(
|
|
1150
|
+
console.log(chalk4.bold(` Review \u2014 task #${issueNumber} "${issue.title}"`));
|
|
1203
1151
|
console.log(divider);
|
|
1204
1152
|
console.log(renderMarkdown(review));
|
|
1205
1153
|
}
|
|
1206
1154
|
console.log(divider + "\n");
|
|
1207
1155
|
let shouldProceed;
|
|
1208
1156
|
try {
|
|
1209
|
-
shouldProceed = await
|
|
1157
|
+
shouldProceed = await select({
|
|
1210
1158
|
message: `Submit task #${issueNumber}?`,
|
|
1211
1159
|
choices: [
|
|
1212
1160
|
{ name: "Yes, submit", value: true },
|
|
@@ -1227,7 +1175,7 @@ async function run(_input, config) {
|
|
|
1227
1175
|
return "Submit cancelled.";
|
|
1228
1176
|
}
|
|
1229
1177
|
if (!commitMessage.trim()) return "Submit cancelled.";
|
|
1230
|
-
spinner =
|
|
1178
|
+
spinner = ora("Committing and pushing\u2026").start();
|
|
1231
1179
|
try {
|
|
1232
1180
|
await stageAllAndCommit(commitMessage.trim());
|
|
1233
1181
|
spinner.stop();
|
|
@@ -1236,52 +1184,71 @@ async function run(_input, config) {
|
|
|
1236
1184
|
return `Commit failed: ${err.message}`;
|
|
1237
1185
|
}
|
|
1238
1186
|
if (isSelfSubmit) {
|
|
1239
|
-
spinner =
|
|
1187
|
+
spinner = ora("Closing issue\u2026").start();
|
|
1240
1188
|
try {
|
|
1241
1189
|
await closeTask(config, issueNumber);
|
|
1242
1190
|
spinner.stop();
|
|
1243
1191
|
} catch (err) {
|
|
1244
1192
|
spinner.stop();
|
|
1245
|
-
console.error(
|
|
1193
|
+
console.error(chalk4.yellow(`Warning: failed to close issue: ${err.message}`));
|
|
1246
1194
|
}
|
|
1247
|
-
setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0 } });
|
|
1195
|
+
setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0, activeBranch: void 0 } });
|
|
1248
1196
|
return `Task #${issueNumber} committed and closed.
|
|
1249
1197
|
Commit: "${commitMessage.trim()}"`;
|
|
1250
1198
|
}
|
|
1251
|
-
spinner =
|
|
1199
|
+
spinner = ora("Checking for existing PR\u2026").start();
|
|
1200
|
+
const existingPR = await getTaskPR(config, issueNumber);
|
|
1201
|
+
spinner.stop();
|
|
1252
1202
|
let prUrl;
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1203
|
+
if (existingPR) {
|
|
1204
|
+
prUrl = existingPR.url;
|
|
1205
|
+
console.log(chalk4.dim(` Existing PR found: ${prUrl} \u2014 updating.`));
|
|
1206
|
+
} else {
|
|
1207
|
+
spinner = ora("Creating pull request\u2026").start();
|
|
1208
|
+
try {
|
|
1209
|
+
await ensureRemoteBranch(config, targetBranch, config.baseBranch ?? "main");
|
|
1210
|
+
const prBody = [
|
|
1211
|
+
`Closes #${issueNumber}`,
|
|
1212
|
+
issue.body ? `
|
|
1257
1213
|
${issue.body}` : "",
|
|
1258
|
-
|
|
1214
|
+
review ? `
|
|
1259
1215
|
## AI Review
|
|
1260
1216
|
${review}` : ""
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1217
|
+
].join("\n").trim();
|
|
1218
|
+
prUrl = await createPR(config, issue.title, prBody, branch, targetBranch);
|
|
1219
|
+
spinner.stop();
|
|
1220
|
+
} catch (err) {
|
|
1221
|
+
spinner.stop();
|
|
1222
|
+
return `Committed but PR creation failed: ${err.message}`;
|
|
1223
|
+
}
|
|
1267
1224
|
}
|
|
1268
|
-
spinner =
|
|
1225
|
+
spinner = ora("Marking as in-review\u2026").start();
|
|
1269
1226
|
try {
|
|
1270
1227
|
await markInReview(config, issueNumber);
|
|
1271
1228
|
spinner.stop();
|
|
1272
1229
|
} catch (err) {
|
|
1273
1230
|
spinner.stop();
|
|
1274
|
-
return `PR created (${prUrl}) but failed to update label: ${err.message}`;
|
|
1231
|
+
return `PR ${existingPR ? "updated" : "created"} (${prUrl}) but failed to update label: ${err.message}`;
|
|
1275
1232
|
}
|
|
1276
|
-
setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0 } });
|
|
1277
|
-
return `Task #${issueNumber} submitted.
|
|
1233
|
+
setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0, activeBranch: void 0 } });
|
|
1234
|
+
return `Task #${issueNumber} ${existingPR ? "re-submitted" : "submitted"}.
|
|
1278
1235
|
Commit: "${commitMessage.trim()}"
|
|
1279
1236
|
PR: ${prUrl}`;
|
|
1280
1237
|
}
|
|
1281
1238
|
async function execute(input3, config) {
|
|
1282
1239
|
const taskState = getConfig().taskState;
|
|
1283
|
-
|
|
1284
|
-
if (!issueNumber)
|
|
1240
|
+
let issueNumber = taskState?.activeIssueNumber;
|
|
1241
|
+
if (!issueNumber) {
|
|
1242
|
+
const currentBranch = await getCurrentBranch();
|
|
1243
|
+
const fromBranch = parseIssueNumberFromBranch(currentBranch);
|
|
1244
|
+
if (fromBranch) {
|
|
1245
|
+
issueNumber = fromBranch;
|
|
1246
|
+
} else {
|
|
1247
|
+
const found = await getIssueNumberFromBranch(config, currentBranch);
|
|
1248
|
+
if (!found) return "No active task found. Claim a task first.";
|
|
1249
|
+
issueNumber = found.issueNumber;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1285
1252
|
const diffPromise = taskState?.baseCommit ? getDiffFromCommit(taskState.baseCommit) : getDiff();
|
|
1286
1253
|
const [issue, diff, branch, me] = await Promise.all([
|
|
1287
1254
|
getTask(config, issueNumber),
|
|
@@ -1289,7 +1256,11 @@ async function execute(input3, config) {
|
|
|
1289
1256
|
getCurrentBranch(),
|
|
1290
1257
|
getAuthenticatedUser(config)
|
|
1291
1258
|
]);
|
|
1292
|
-
const
|
|
1259
|
+
const targetBranch = extractTargetBranch(issue.body) ?? makeWorkerBranchName(issue.author ?? me);
|
|
1260
|
+
const openSubtaskNumbers = await getOpenSubtasks(config, branch);
|
|
1261
|
+
if (openSubtaskNumbers.length > 0) {
|
|
1262
|
+
return `Cannot submit: ${openSubtaskNumbers.length} sub-task(s) still open: ` + openSubtaskNumbers.map((n) => `#${n}`).join(", ");
|
|
1263
|
+
}
|
|
1293
1264
|
const isSelfSubmit = issue.author !== null && issue.author === me;
|
|
1294
1265
|
let review = "";
|
|
1295
1266
|
if (!isSelfSubmit) {
|
|
@@ -1310,29 +1281,36 @@ async function execute(input3, config) {
|
|
|
1310
1281
|
await closeTask(config, issueNumber);
|
|
1311
1282
|
} catch {
|
|
1312
1283
|
}
|
|
1313
|
-
setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0 } });
|
|
1284
|
+
setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0, activeBranch: void 0 } });
|
|
1314
1285
|
return `Task #${issueNumber} committed and closed.
|
|
1315
1286
|
Commit: "${commitMessage}"`;
|
|
1316
1287
|
}
|
|
1288
|
+
const existingPR = await getTaskPR(config, issueNumber);
|
|
1317
1289
|
let prUrl;
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1290
|
+
if (existingPR) {
|
|
1291
|
+
prUrl = existingPR.url;
|
|
1292
|
+
} else {
|
|
1293
|
+
try {
|
|
1294
|
+
await ensureRemoteBranch(config, targetBranch, config.baseBranch ?? "main");
|
|
1295
|
+
const prBody = [
|
|
1296
|
+
`Closes #${issueNumber}`,
|
|
1297
|
+
issue.body ? `
|
|
1322
1298
|
${issue.body}` : "",
|
|
1323
|
-
|
|
1299
|
+
review ? `
|
|
1324
1300
|
## AI Review
|
|
1325
1301
|
${review}` : ""
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1302
|
+
].join("\n").trim();
|
|
1303
|
+
prUrl = await createPR(config, issue.title, prBody, branch, targetBranch);
|
|
1304
|
+
} catch (err) {
|
|
1305
|
+
return `Committed but PR creation failed: ${err.message}`;
|
|
1306
|
+
}
|
|
1330
1307
|
}
|
|
1331
1308
|
try {
|
|
1332
1309
|
await markInReview(config, issueNumber);
|
|
1333
1310
|
} catch {
|
|
1334
1311
|
}
|
|
1335
|
-
|
|
1312
|
+
setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0, activeBranch: void 0 } });
|
|
1313
|
+
return `Task #${issueNumber} ${existingPR ? "re-submitted" : "submitted"}.
|
|
1336
1314
|
Review:
|
|
1337
1315
|
${review}
|
|
1338
1316
|
Commit: "${commitMessage}"
|
|
@@ -1349,8 +1327,8 @@ __export(close_exports, {
|
|
|
1349
1327
|
terminal: () => terminal2
|
|
1350
1328
|
});
|
|
1351
1329
|
init_github();
|
|
1352
|
-
import { select as
|
|
1353
|
-
import
|
|
1330
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
1331
|
+
import ora2 from "ora";
|
|
1354
1332
|
var definition2 = {
|
|
1355
1333
|
type: "function",
|
|
1356
1334
|
function: {
|
|
@@ -1376,7 +1354,7 @@ async function run2(input3, config) {
|
|
|
1376
1354
|
}
|
|
1377
1355
|
if (tasks.length === 0) return "No tasks found.";
|
|
1378
1356
|
try {
|
|
1379
|
-
issueNumber = await
|
|
1357
|
+
issueNumber = await select2({
|
|
1380
1358
|
message: "Select task to close:",
|
|
1381
1359
|
choices: tasks.map((t) => ({ name: `#${t.number} [${getStatus(t)}] ${t.title}`, value: t.number }))
|
|
1382
1360
|
});
|
|
@@ -1386,7 +1364,7 @@ async function run2(input3, config) {
|
|
|
1386
1364
|
}
|
|
1387
1365
|
let confirmed;
|
|
1388
1366
|
try {
|
|
1389
|
-
confirmed = await
|
|
1367
|
+
confirmed = await select2({
|
|
1390
1368
|
message: `Close task #${issueNumber}?`,
|
|
1391
1369
|
choices: [
|
|
1392
1370
|
{ name: "Yes, close it", value: true },
|
|
@@ -1397,7 +1375,7 @@ async function run2(input3, config) {
|
|
|
1397
1375
|
return "Cancelled.";
|
|
1398
1376
|
}
|
|
1399
1377
|
if (!confirmed) return "Cancelled.";
|
|
1400
|
-
const spinner =
|
|
1378
|
+
const spinner = ora2(`Closing #${issueNumber}\u2026`).start();
|
|
1401
1379
|
try {
|
|
1402
1380
|
await closeTask(config, issueNumber);
|
|
1403
1381
|
spinner.stop();
|
|
@@ -1409,7 +1387,7 @@ async function run2(input3, config) {
|
|
|
1409
1387
|
}
|
|
1410
1388
|
async function execute2(input3, config) {
|
|
1411
1389
|
const issueNumber = input3["issue_number"];
|
|
1412
|
-
const spinner =
|
|
1390
|
+
const spinner = ora2(`Closing #${issueNumber}\u2026`).start();
|
|
1413
1391
|
try {
|
|
1414
1392
|
await closeTask(config, issueNumber);
|
|
1415
1393
|
spinner.stop();
|
|
@@ -1455,7 +1433,7 @@ async function run3(input3, config) {
|
|
|
1455
1433
|
}
|
|
1456
1434
|
if (tasks.length === 0) return "No tasks found.";
|
|
1457
1435
|
try {
|
|
1458
|
-
chosenNumber = await
|
|
1436
|
+
chosenNumber = await select3({
|
|
1459
1437
|
message: "Select a task:",
|
|
1460
1438
|
choices: tasks.map((t) => ({
|
|
1461
1439
|
name: `#${String(t.number).padEnd(4)} ${colorStatus(getStatus(t))} ${t.title}`,
|
|
@@ -1479,9 +1457,9 @@ async function run3(input3, config) {
|
|
|
1479
1457
|
const comments = await listComments(config, issue.number, 1);
|
|
1480
1458
|
if (comments.length > 0) {
|
|
1481
1459
|
const c = comments[0];
|
|
1482
|
-
const divider =
|
|
1460
|
+
const divider = chalk5.dim("\u2500".repeat(70));
|
|
1483
1461
|
console.log(
|
|
1484
|
-
|
|
1462
|
+
chalk5.red.bold(" Latest rejection feedback") + chalk5.dim(` \u2014 @${c.author} \xB7 ${c.createdAt.slice(0, 10)}`)
|
|
1485
1463
|
);
|
|
1486
1464
|
console.log(divider);
|
|
1487
1465
|
console.log(renderMarkdown(c.body));
|
|
@@ -1491,22 +1469,36 @@ async function run3(input3, config) {
|
|
|
1491
1469
|
}
|
|
1492
1470
|
}
|
|
1493
1471
|
const actions = [];
|
|
1494
|
-
if (status === "available")
|
|
1495
|
-
|
|
1472
|
+
if (status === "available") {
|
|
1473
|
+
actions.push({ name: "Claim this task", value: "claim" });
|
|
1474
|
+
}
|
|
1475
|
+
if (status === "claimed") {
|
|
1476
|
+
actions.push({ name: "Submit this task", value: "submit" });
|
|
1477
|
+
}
|
|
1478
|
+
if (status === "changes-needed") {
|
|
1479
|
+
const { getAuthenticatedUser: getAuthenticatedUser2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
1480
|
+
const me = await getAuthenticatedUser2(config);
|
|
1481
|
+
const taskBranch = issue.assignee ? makeTaskBranchName(issue.number, issue.assignee) : makeTaskBranchName(issue.number, me);
|
|
1482
|
+
const currentBranch = await getCurrentBranch();
|
|
1483
|
+
if (currentBranch === taskBranch) {
|
|
1484
|
+
actions.push({ name: "Submit this task (fixes done)", value: "submit" });
|
|
1485
|
+
} else {
|
|
1486
|
+
actions.push({ name: `Switch to ${taskBranch} to fix`, value: "switch-fix" });
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1496
1489
|
actions.push({ name: "Close this task", value: "close" });
|
|
1497
1490
|
actions.push({ name: "Nothing, just viewing", value: "none" });
|
|
1498
1491
|
let action;
|
|
1499
1492
|
try {
|
|
1500
|
-
action = await
|
|
1493
|
+
action = await select3({ message: "Action:", choices: actions });
|
|
1501
1494
|
} catch {
|
|
1502
1495
|
return "Cancelled.";
|
|
1503
1496
|
}
|
|
1504
1497
|
if (action === "none") return `Viewed task #${issue.number}.`;
|
|
1505
1498
|
if (action === "claim") {
|
|
1506
1499
|
try {
|
|
1507
|
-
const
|
|
1508
|
-
const
|
|
1509
|
-
const myTasks = await listMyTasks2(config, me);
|
|
1500
|
+
const me = await getAuthenticatedUser(config);
|
|
1501
|
+
const myTasks = await listMyTasks(config, me);
|
|
1510
1502
|
const activeTask = myTasks.find((t) => {
|
|
1511
1503
|
const labels = t.labels;
|
|
1512
1504
|
return labels.includes("techunter:claimed") || labels.includes("techunter:changes-needed");
|
|
@@ -1515,37 +1507,65 @@ async function run3(input3, config) {
|
|
|
1515
1507
|
return `You already have an active task: #${activeTask.number} "${activeTask.title}"
|
|
1516
1508
|
Finish or submit it before claiming a new one.`;
|
|
1517
1509
|
}
|
|
1518
|
-
let
|
|
1510
|
+
let stashed = false;
|
|
1511
|
+
if (await hasUncommittedChanges()) {
|
|
1512
|
+
let choice;
|
|
1513
|
+
try {
|
|
1514
|
+
choice = await select3({
|
|
1515
|
+
message: "You have uncommitted changes. What would you like to do?",
|
|
1516
|
+
choices: [
|
|
1517
|
+
{ name: "Stash changes and switch branch (restore with: git stash pop)", value: "stash" },
|
|
1518
|
+
{ name: "Cancel", value: "cancel" }
|
|
1519
|
+
]
|
|
1520
|
+
});
|
|
1521
|
+
} catch {
|
|
1522
|
+
choice = "cancel";
|
|
1523
|
+
}
|
|
1524
|
+
if (choice === "cancel") return "Cancelled.";
|
|
1525
|
+
await stash(`tch: before claiming #${issue.number}`);
|
|
1526
|
+
stashed = true;
|
|
1527
|
+
console.log(chalk5.dim(" Changes stashed. Run `git stash pop` after you finish this task to restore them."));
|
|
1528
|
+
}
|
|
1529
|
+
let spinner = ora3(`Claiming #${issue.number}\u2026`).start();
|
|
1519
1530
|
await claimTask(config, issue.number, me);
|
|
1520
1531
|
spinner.stop();
|
|
1521
|
-
const
|
|
1532
|
+
const taskBranch = makeTaskBranchName(issue.number, me);
|
|
1522
1533
|
const taskBase = extractBaseCommit(issue.body);
|
|
1523
|
-
spinner =
|
|
1534
|
+
spinner = ora3(`Creating branch ${taskBranch}${taskBase ? ` from ${taskBase.slice(0, 7)}` : ""}\u2026`).start();
|
|
1524
1535
|
try {
|
|
1525
1536
|
if (taskBase) {
|
|
1526
|
-
await
|
|
1537
|
+
await checkoutFromCommit(taskBranch, taskBase);
|
|
1527
1538
|
} else {
|
|
1528
|
-
await switchToBranchOrCreate(
|
|
1539
|
+
await switchToBranchOrCreate(taskBranch);
|
|
1529
1540
|
}
|
|
1530
1541
|
spinner.stop();
|
|
1531
|
-
spinner =
|
|
1542
|
+
spinner = ora3("Pushing task branch\u2026").start();
|
|
1532
1543
|
try {
|
|
1533
|
-
await pushBranch(
|
|
1544
|
+
await pushBranch(taskBranch);
|
|
1534
1545
|
spinner.stop();
|
|
1535
1546
|
} catch {
|
|
1536
|
-
spinner.warn("Could not push
|
|
1547
|
+
spinner.warn("Could not push task branch \u2014 will push on submit");
|
|
1537
1548
|
}
|
|
1538
|
-
} catch {
|
|
1539
|
-
spinner.warn(`Could not switch to ${
|
|
1549
|
+
} catch (err) {
|
|
1550
|
+
spinner.warn(`Could not switch to ${taskBranch}`);
|
|
1551
|
+
if (stashed) {
|
|
1552
|
+
try {
|
|
1553
|
+
await stashPop();
|
|
1554
|
+
console.log(chalk5.dim(" Restored stashed changes."));
|
|
1555
|
+
} catch {
|
|
1556
|
+
console.log(chalk5.yellow(" Warning: could not restore stash automatically. Run `git stash pop` manually."));
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
throw err;
|
|
1540
1560
|
}
|
|
1541
1561
|
const baseCommit = await getCurrentCommit();
|
|
1542
|
-
setConfig({ taskState: { activeIssueNumber: issue.number, baseCommit } });
|
|
1543
|
-
console.log(
|
|
1544
|
-
Claimed! Branch: ${
|
|
1562
|
+
setConfig({ taskState: { activeIssueNumber: issue.number, baseCommit, activeBranch: taskBranch } });
|
|
1563
|
+
console.log(chalk5.green(`
|
|
1564
|
+
Claimed! Branch: ${taskBranch} (base: ${baseCommit.slice(0, 7)})
|
|
1545
1565
|
`));
|
|
1546
1566
|
let openClaude;
|
|
1547
1567
|
try {
|
|
1548
|
-
openClaude = await
|
|
1568
|
+
openClaude = await select3({
|
|
1549
1569
|
message: "Open Claude Code for this task?",
|
|
1550
1570
|
choices: [
|
|
1551
1571
|
{ name: "Yes, start coding now", value: true },
|
|
@@ -1555,12 +1575,58 @@ Finish or submit it before claiming a new one.`;
|
|
|
1555
1575
|
} catch {
|
|
1556
1576
|
openClaude = false;
|
|
1557
1577
|
}
|
|
1558
|
-
if (openClaude) await launchClaudeCode(issue,
|
|
1559
|
-
return `Task #${issue.number} claimed. Branch: ${
|
|
1578
|
+
if (openClaude) await launchClaudeCode(issue, taskBranch);
|
|
1579
|
+
return `Task #${issue.number} claimed. Branch: ${taskBranch}`;
|
|
1560
1580
|
} catch (err) {
|
|
1561
1581
|
return `Error claiming task: ${err.message}`;
|
|
1562
1582
|
}
|
|
1563
1583
|
}
|
|
1584
|
+
if (action === "switch-fix") {
|
|
1585
|
+
const { getAuthenticatedUser: getAuthenticatedUser2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
1586
|
+
const me = await getAuthenticatedUser2(config);
|
|
1587
|
+
const taskBranch = issue.assignee ? makeTaskBranchName(issue.number, issue.assignee) : makeTaskBranchName(issue.number, me);
|
|
1588
|
+
let stashed = false;
|
|
1589
|
+
if (await hasUncommittedChanges()) {
|
|
1590
|
+
let choice;
|
|
1591
|
+
try {
|
|
1592
|
+
choice = await select3({
|
|
1593
|
+
message: "You have uncommitted changes. What would you like to do?",
|
|
1594
|
+
choices: [
|
|
1595
|
+
{ name: "Stash changes and switch branch (restore with: git stash pop)", value: "stash" },
|
|
1596
|
+
{ name: "Cancel", value: "cancel" }
|
|
1597
|
+
]
|
|
1598
|
+
});
|
|
1599
|
+
} catch {
|
|
1600
|
+
choice = "cancel";
|
|
1601
|
+
}
|
|
1602
|
+
if (choice === "cancel") return "Cancelled.";
|
|
1603
|
+
await stash(`tch: before switching to ${taskBranch}`);
|
|
1604
|
+
stashed = true;
|
|
1605
|
+
console.log(chalk5.dim(" Changes stashed. Run `git stash pop` to restore them later."));
|
|
1606
|
+
}
|
|
1607
|
+
const spinner = ora3(`Switching to ${taskBranch}\u2026`).start();
|
|
1608
|
+
try {
|
|
1609
|
+
await switchToBranchOrCreate(taskBranch);
|
|
1610
|
+
spinner.stop();
|
|
1611
|
+
} catch (err) {
|
|
1612
|
+
spinner.warn(`Could not switch to ${taskBranch}: ${err.message}`);
|
|
1613
|
+
if (stashed) {
|
|
1614
|
+
try {
|
|
1615
|
+
await stashPop();
|
|
1616
|
+
console.log(chalk5.dim(" Restored stashed changes."));
|
|
1617
|
+
} catch {
|
|
1618
|
+
console.log(chalk5.yellow(" Run `git stash pop` manually to restore your changes."));
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
return `Error: ${err.message}`;
|
|
1622
|
+
}
|
|
1623
|
+
const baseCommit = extractBaseCommit(issue.body) ?? await getCurrentCommit();
|
|
1624
|
+
setConfig({ taskState: { activeIssueNumber: issue.number, baseCommit, activeBranch: taskBranch } });
|
|
1625
|
+
console.log(chalk5.green(`
|
|
1626
|
+
Switched to ${taskBranch}. Fix the issues then run /submit.
|
|
1627
|
+
`));
|
|
1628
|
+
return `Switched to ${taskBranch} for task #${issue.number}.`;
|
|
1629
|
+
}
|
|
1564
1630
|
if (action === "submit") return run({}, config);
|
|
1565
1631
|
if (action === "close") return run2({ issue_number: issue.number }, config);
|
|
1566
1632
|
return "Cancelled.";
|
|
@@ -1589,28 +1655,31 @@ async function execute3(input3, config) {
|
|
|
1589
1655
|
if (activeTask) {
|
|
1590
1656
|
return `You already have an active task: #${activeTask.number} "${activeTask.title}". Finish it before claiming a new one.`;
|
|
1591
1657
|
}
|
|
1658
|
+
if (await hasUncommittedChanges()) {
|
|
1659
|
+
return "Cannot claim: you have uncommitted changes. Commit or stash them first (git stash).";
|
|
1660
|
+
}
|
|
1592
1661
|
try {
|
|
1593
1662
|
await claimTask(config, issueNumber, me);
|
|
1594
1663
|
} catch (err) {
|
|
1595
1664
|
return `Error claiming task: ${err.message}`;
|
|
1596
1665
|
}
|
|
1597
|
-
const
|
|
1666
|
+
const taskBranch = makeTaskBranchName(issue.number, me);
|
|
1598
1667
|
const taskBase = extractBaseCommit(issue.body);
|
|
1599
1668
|
try {
|
|
1600
1669
|
if (taskBase) {
|
|
1601
|
-
await
|
|
1670
|
+
await checkoutFromCommit(taskBranch, taskBase);
|
|
1602
1671
|
} else {
|
|
1603
|
-
await switchToBranchOrCreate(
|
|
1672
|
+
await switchToBranchOrCreate(taskBranch);
|
|
1604
1673
|
}
|
|
1605
1674
|
} catch {
|
|
1606
1675
|
}
|
|
1607
1676
|
try {
|
|
1608
|
-
await pushBranch(
|
|
1677
|
+
await pushBranch(taskBranch);
|
|
1609
1678
|
} catch {
|
|
1610
1679
|
}
|
|
1611
1680
|
const baseCommit = await getCurrentCommit();
|
|
1612
|
-
setConfig({ taskState: { activeIssueNumber: issueNumber, baseCommit } });
|
|
1613
|
-
return `Task #${issueNumber} claimed. Branch: ${
|
|
1681
|
+
setConfig({ taskState: { activeIssueNumber: issueNumber, baseCommit, activeBranch: taskBranch } });
|
|
1682
|
+
return `Task #${issueNumber} claimed. Branch: ${taskBranch} (base commit: ${baseCommit.slice(0, 7)})`;
|
|
1614
1683
|
}
|
|
1615
1684
|
return `Unknown action: ${action}`;
|
|
1616
1685
|
}
|
|
@@ -1625,14 +1694,14 @@ __export(new_task_exports, {
|
|
|
1625
1694
|
terminal: () => terminal4
|
|
1626
1695
|
});
|
|
1627
1696
|
init_github();
|
|
1628
|
-
import { select as
|
|
1697
|
+
import { select as select4, input as promptInput2 } from "@inquirer/prompts";
|
|
1629
1698
|
import { writeFile, readFile, mkdtemp, rm } from "fs/promises";
|
|
1630
1699
|
import { spawn as spawn2 } from "child_process";
|
|
1631
1700
|
import { tmpdir } from "os";
|
|
1632
1701
|
import path from "path";
|
|
1633
|
-
import
|
|
1634
|
-
import
|
|
1635
|
-
import
|
|
1702
|
+
import ora4 from "ora";
|
|
1703
|
+
import chalk6 from "chalk";
|
|
1704
|
+
import open from "open";
|
|
1636
1705
|
|
|
1637
1706
|
// src/tools/new-task/prompts.ts
|
|
1638
1707
|
var GUIDE_FORMAT = `
|
|
@@ -1685,6 +1754,77 @@ async function openInEditor(content) {
|
|
|
1685
1754
|
await rm(dir, { recursive: true, force: true });
|
|
1686
1755
|
}
|
|
1687
1756
|
}
|
|
1757
|
+
async function resolveBaseAndTarget(config, me, interactive) {
|
|
1758
|
+
const currentBranch = await getCurrentBranch();
|
|
1759
|
+
if (isTaskBranch(currentBranch)) {
|
|
1760
|
+
if (await hasUncommittedChanges()) {
|
|
1761
|
+
if (!interactive) {
|
|
1762
|
+
throw new Error("Cannot create sub-task: you have uncommitted changes. Commit them first so the executor starts from the correct base.");
|
|
1763
|
+
}
|
|
1764
|
+
const { select: inquirerSelect } = await import("@inquirer/prompts");
|
|
1765
|
+
let choice;
|
|
1766
|
+
try {
|
|
1767
|
+
choice = await inquirerSelect({
|
|
1768
|
+
message: "You have uncommitted changes. The sub-task executor will start from the last commit \u2014 they won't see your current unsaved work.",
|
|
1769
|
+
choices: [
|
|
1770
|
+
{ name: "Commit first (cancel and commit manually)", value: "cancel" },
|
|
1771
|
+
{ name: "Continue anyway (executor starts without my unsaved changes)", value: "continue" }
|
|
1772
|
+
]
|
|
1773
|
+
});
|
|
1774
|
+
} catch {
|
|
1775
|
+
choice = "cancel";
|
|
1776
|
+
}
|
|
1777
|
+
if (choice === "cancel") throw new Error("Cancelled. Commit your changes first, then create the sub-task.");
|
|
1778
|
+
}
|
|
1779
|
+
const baseCommit2 = await getCurrentCommit();
|
|
1780
|
+
return { baseCommit: baseCommit2, targetBranch: currentBranch, isSubtask: true };
|
|
1781
|
+
}
|
|
1782
|
+
let stashedForSync = false;
|
|
1783
|
+
if (await hasUncommittedChanges()) {
|
|
1784
|
+
if (!interactive) {
|
|
1785
|
+
throw new Error("Cannot create task: you have uncommitted changes. Commit or stash them first (git stash).");
|
|
1786
|
+
}
|
|
1787
|
+
const { select: inquirerSelect } = await import("@inquirer/prompts");
|
|
1788
|
+
let choice;
|
|
1789
|
+
try {
|
|
1790
|
+
choice = await inquirerSelect({
|
|
1791
|
+
message: "You have uncommitted changes. Syncing with main requires a clean working tree.",
|
|
1792
|
+
choices: [
|
|
1793
|
+
{ name: "Stash changes and continue (restore with: git stash pop)", value: "stash" },
|
|
1794
|
+
{ name: "Cancel", value: "cancel" }
|
|
1795
|
+
]
|
|
1796
|
+
});
|
|
1797
|
+
} catch {
|
|
1798
|
+
choice = "cancel";
|
|
1799
|
+
}
|
|
1800
|
+
if (choice === "cancel") throw new Error("Cancelled.");
|
|
1801
|
+
await stash("tch: before creating new task");
|
|
1802
|
+
stashedForSync = true;
|
|
1803
|
+
console.log(chalk6.dim(" Changes stashed. Run `git stash pop` after creating the task."));
|
|
1804
|
+
}
|
|
1805
|
+
const baseBranch = config.baseBranch ?? "main";
|
|
1806
|
+
let baseCommit;
|
|
1807
|
+
const syncSpinner = ora4(`Syncing with ${baseBranch}\u2026`).start();
|
|
1808
|
+
try {
|
|
1809
|
+
await syncWithBase(baseBranch);
|
|
1810
|
+
baseCommit = await getCurrentCommit();
|
|
1811
|
+
syncSpinner.succeed(`Synced with ${baseBranch} (base: ${baseCommit.slice(0, 7)})`);
|
|
1812
|
+
} catch {
|
|
1813
|
+
syncSpinner.warn(`Could not sync with ${baseBranch} \u2014 recording remote HEAD as base`);
|
|
1814
|
+
try {
|
|
1815
|
+
baseCommit = await getRemoteHeadSha(baseBranch);
|
|
1816
|
+
} catch {
|
|
1817
|
+
}
|
|
1818
|
+
if (stashedForSync) {
|
|
1819
|
+
try {
|
|
1820
|
+
await stashPop();
|
|
1821
|
+
} catch {
|
|
1822
|
+
}
|
|
1823
|
+
throw new Error(`Could not sync with ${baseBranch}. Your changes have been restored from stash.`);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
return { baseCommit, targetBranch: makeWorkerBranchName(me), isSubtask: false };
|
|
1827
|
+
}
|
|
1688
1828
|
var definition4 = {
|
|
1689
1829
|
type: "function",
|
|
1690
1830
|
function: {
|
|
@@ -1701,7 +1841,7 @@ var definition4 = {
|
|
|
1701
1841
|
}
|
|
1702
1842
|
};
|
|
1703
1843
|
async function run4(input3, config) {
|
|
1704
|
-
const authSpinner =
|
|
1844
|
+
const authSpinner = ora4("Checking permissions\u2026").start();
|
|
1705
1845
|
let me;
|
|
1706
1846
|
let allowed;
|
|
1707
1847
|
try {
|
|
@@ -1724,7 +1864,7 @@ async function run4(input3, config) {
|
|
|
1724
1864
|
}
|
|
1725
1865
|
if (!title) return "Cancelled.";
|
|
1726
1866
|
}
|
|
1727
|
-
const spinner =
|
|
1867
|
+
const spinner = ora4("Scanning project and generating guide\u2026").start();
|
|
1728
1868
|
let guide;
|
|
1729
1869
|
try {
|
|
1730
1870
|
guide = await generateGuide(config, title);
|
|
@@ -1733,16 +1873,16 @@ async function run4(input3, config) {
|
|
|
1733
1873
|
spinner.stop();
|
|
1734
1874
|
return `Error generating guide: ${err.message}`;
|
|
1735
1875
|
}
|
|
1736
|
-
const divider =
|
|
1876
|
+
const divider = chalk6.dim("\u2500".repeat(70));
|
|
1737
1877
|
for (; ; ) {
|
|
1738
1878
|
console.log("\n" + divider);
|
|
1739
|
-
console.log(
|
|
1879
|
+
console.log(chalk6.bold(" Generated guide preview"));
|
|
1740
1880
|
console.log(divider);
|
|
1741
1881
|
console.log(renderMarkdown(guide));
|
|
1742
1882
|
console.log(divider + "\n");
|
|
1743
1883
|
let action;
|
|
1744
1884
|
try {
|
|
1745
|
-
action = await
|
|
1885
|
+
action = await select4({
|
|
1746
1886
|
message: "Create this task?",
|
|
1747
1887
|
choices: [
|
|
1748
1888
|
{ name: "Yes, create task", value: "create" },
|
|
@@ -1760,7 +1900,7 @@ async function run4(input3, config) {
|
|
|
1760
1900
|
try {
|
|
1761
1901
|
guide = await openInEditor(guide);
|
|
1762
1902
|
} catch (err) {
|
|
1763
|
-
console.log(
|
|
1903
|
+
console.log(chalk6.yellow(` Editor error: ${err.message}`));
|
|
1764
1904
|
}
|
|
1765
1905
|
continue;
|
|
1766
1906
|
}
|
|
@@ -1771,35 +1911,32 @@ async function run4(input3, config) {
|
|
|
1771
1911
|
return "Cancelled.";
|
|
1772
1912
|
}
|
|
1773
1913
|
if (!feedback) continue;
|
|
1774
|
-
const reviseSpinner =
|
|
1914
|
+
const reviseSpinner = ora4("Revising guide\u2026").start();
|
|
1775
1915
|
try {
|
|
1776
1916
|
guide = await generateGuide(config, title, { feedback, previousGuide: guide });
|
|
1777
1917
|
reviseSpinner.stop();
|
|
1778
1918
|
} catch (err) {
|
|
1779
1919
|
reviseSpinner.stop();
|
|
1780
|
-
console.log(
|
|
1920
|
+
console.log(chalk6.yellow(` Revision error: ${err.message}`));
|
|
1781
1921
|
}
|
|
1782
1922
|
}
|
|
1783
|
-
const baseBranch = config.baseBranch ?? "main";
|
|
1784
1923
|
let baseCommit;
|
|
1785
|
-
|
|
1924
|
+
let targetBranch;
|
|
1925
|
+
let isSubtask;
|
|
1786
1926
|
try {
|
|
1787
|
-
await
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
baseCommit = await getRemoteHeadSha(baseBranch);
|
|
1794
|
-
} catch {
|
|
1795
|
-
}
|
|
1927
|
+
({ baseCommit, targetBranch, isSubtask } = await resolveBaseAndTarget(config, me, true));
|
|
1928
|
+
} catch (err) {
|
|
1929
|
+
return err.message;
|
|
1930
|
+
}
|
|
1931
|
+
if (isSubtask) {
|
|
1932
|
+
console.log(chalk6.dim(` Sub-task: will target branch ${chalk6.cyan(targetBranch)} (base: ${baseCommit?.slice(0, 7) ?? "HEAD"})`));
|
|
1796
1933
|
}
|
|
1797
|
-
const createSpinner =
|
|
1934
|
+
const createSpinner = ora4(`Creating "${title}"\u2026`).start();
|
|
1798
1935
|
let htmlUrl;
|
|
1799
1936
|
let issueNumber;
|
|
1800
1937
|
let issueTitle;
|
|
1801
1938
|
try {
|
|
1802
|
-
const issue = await createTask(config, title, guide, baseCommit);
|
|
1939
|
+
const issue = await createTask(config, title, guide, baseCommit, targetBranch);
|
|
1803
1940
|
createSpinner.stop();
|
|
1804
1941
|
htmlUrl = issue.htmlUrl;
|
|
1805
1942
|
issueNumber = issue.number;
|
|
@@ -1808,19 +1945,19 @@ async function run4(input3, config) {
|
|
|
1808
1945
|
createSpinner.stop();
|
|
1809
1946
|
return `Error: ${err.message}`;
|
|
1810
1947
|
}
|
|
1811
|
-
console.log(
|
|
1948
|
+
console.log(chalk6.green(`
|
|
1812
1949
|
Created #${issueNumber} "${issueTitle}"
|
|
1813
|
-
${
|
|
1950
|
+
${chalk6.dim(htmlUrl)}
|
|
1814
1951
|
`));
|
|
1815
1952
|
try {
|
|
1816
|
-
const openBrowser = await
|
|
1953
|
+
const openBrowser = await select4({
|
|
1817
1954
|
message: "Open issue in browser?",
|
|
1818
1955
|
choices: [
|
|
1819
1956
|
{ name: "Yes", value: true },
|
|
1820
1957
|
{ name: "No", value: false }
|
|
1821
1958
|
]
|
|
1822
1959
|
});
|
|
1823
|
-
if (openBrowser) await
|
|
1960
|
+
if (openBrowser) await open(htmlUrl);
|
|
1824
1961
|
} catch {
|
|
1825
1962
|
}
|
|
1826
1963
|
return `Created #${issueNumber} "${issueTitle}" \u2014 ${htmlUrl}`;
|
|
@@ -1836,19 +1973,9 @@ async function execute4(input3, config) {
|
|
|
1836
1973
|
if (feedback) {
|
|
1837
1974
|
guide = await generateGuide(config, title, { feedback, previousGuide: guide });
|
|
1838
1975
|
}
|
|
1839
|
-
const
|
|
1840
|
-
let baseCommit;
|
|
1841
|
-
try {
|
|
1842
|
-
await syncWithBase(baseBranch);
|
|
1843
|
-
baseCommit = await getCurrentCommit();
|
|
1844
|
-
} catch {
|
|
1845
|
-
try {
|
|
1846
|
-
baseCommit = await getRemoteHeadSha(baseBranch);
|
|
1847
|
-
} catch {
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1976
|
+
const { baseCommit, targetBranch } = await resolveBaseAndTarget(config, me, false);
|
|
1850
1977
|
try {
|
|
1851
|
-
const issue = await createTask(config, title, guide, baseCommit);
|
|
1978
|
+
const issue = await createTask(config, title, guide, baseCommit, targetBranch);
|
|
1852
1979
|
return `Created #${issue.number} "${issue.title}" \u2014 ${issue.htmlUrl}
|
|
1853
1980
|
|
|
1854
1981
|
Guide:
|
|
@@ -1868,7 +1995,7 @@ __export(my_status_exports, {
|
|
|
1868
1995
|
terminal: () => terminal5
|
|
1869
1996
|
});
|
|
1870
1997
|
init_github();
|
|
1871
|
-
import
|
|
1998
|
+
import ora5 from "ora";
|
|
1872
1999
|
var definition5 = {
|
|
1873
2000
|
type: "function",
|
|
1874
2001
|
function: {
|
|
@@ -1878,7 +2005,7 @@ var definition5 = {
|
|
|
1878
2005
|
}
|
|
1879
2006
|
};
|
|
1880
2007
|
async function run5(_input, config) {
|
|
1881
|
-
const spinner =
|
|
2008
|
+
const spinner = ora5("Fetching your tasks\u2026").start();
|
|
1882
2009
|
try {
|
|
1883
2010
|
const me = await getAuthenticatedUser(config);
|
|
1884
2011
|
const tasks = await listMyTasks(config, me);
|
|
@@ -1898,120 +2025,193 @@ var terminal5 = true;
|
|
|
1898
2025
|
// src/tools/review/index.ts
|
|
1899
2026
|
var review_exports = {};
|
|
1900
2027
|
__export(review_exports, {
|
|
2028
|
+
definition: () => definition8,
|
|
2029
|
+
execute: () => execute8,
|
|
2030
|
+
run: () => run8,
|
|
2031
|
+
terminal: () => terminal8
|
|
2032
|
+
});
|
|
2033
|
+
init_github();
|
|
2034
|
+
import chalk9 from "chalk";
|
|
2035
|
+
import ora8 from "ora";
|
|
2036
|
+
import { select as select7 } from "@inquirer/prompts";
|
|
2037
|
+
|
|
2038
|
+
// src/tools/accept/index.ts
|
|
2039
|
+
var accept_exports = {};
|
|
2040
|
+
__export(accept_exports, {
|
|
1901
2041
|
definition: () => definition6,
|
|
1902
2042
|
execute: () => execute6,
|
|
1903
2043
|
run: () => run6,
|
|
1904
2044
|
terminal: () => terminal6
|
|
1905
2045
|
});
|
|
1906
2046
|
init_github();
|
|
1907
|
-
import
|
|
2047
|
+
import chalk7 from "chalk";
|
|
2048
|
+
import { select as select5 } from "@inquirer/prompts";
|
|
2049
|
+
import ora6 from "ora";
|
|
1908
2050
|
var definition6 = {
|
|
1909
2051
|
type: "function",
|
|
1910
2052
|
function: {
|
|
1911
|
-
name: "
|
|
1912
|
-
description: "
|
|
1913
|
-
parameters: {
|
|
2053
|
+
name: "accept",
|
|
2054
|
+
description: "Accept an in-review task: merges the PR into the target branch and closes the issue.",
|
|
2055
|
+
parameters: {
|
|
2056
|
+
type: "object",
|
|
2057
|
+
properties: {
|
|
2058
|
+
issue_number: { type: "number", description: "GitHub issue number to accept" }
|
|
2059
|
+
},
|
|
2060
|
+
required: ["issue_number"]
|
|
2061
|
+
}
|
|
1914
2062
|
}
|
|
1915
2063
|
};
|
|
1916
|
-
async function run6(
|
|
1917
|
-
|
|
2064
|
+
async function run6(input3, config) {
|
|
2065
|
+
let issueNumber = input3["issue_number"];
|
|
2066
|
+
if (!issueNumber) {
|
|
2067
|
+
const spinner3 = ora6("Loading tasks for review\u2026").start();
|
|
2068
|
+
let tasks;
|
|
2069
|
+
let me;
|
|
2070
|
+
try {
|
|
2071
|
+
me = await getAuthenticatedUser(config);
|
|
2072
|
+
tasks = await listTasksForReview(config, me);
|
|
2073
|
+
spinner3.stop();
|
|
2074
|
+
} catch (err) {
|
|
2075
|
+
spinner3.stop();
|
|
2076
|
+
return `Error: ${err.message}`;
|
|
2077
|
+
}
|
|
2078
|
+
if (tasks.length === 0) return "No tasks pending review.";
|
|
2079
|
+
try {
|
|
2080
|
+
issueNumber = await select5({
|
|
2081
|
+
message: "Which task to accept?",
|
|
2082
|
+
choices: tasks.map((t) => ({
|
|
2083
|
+
name: `#${t.number} @${t.assignee ?? "\u2014"} ${t.title}`,
|
|
2084
|
+
value: t.number
|
|
2085
|
+
}))
|
|
2086
|
+
});
|
|
2087
|
+
} catch {
|
|
2088
|
+
return "Cancelled.";
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
const spinner2 = ora6("Verifying permissions\u2026").start();
|
|
2092
|
+
let me2;
|
|
2093
|
+
let issue;
|
|
1918
2094
|
try {
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
return `Tasks pending review (created by @${me}):
|
|
1925
|
-
${lines.join("\n")}`;
|
|
2095
|
+
[me2, issue] = await Promise.all([
|
|
2096
|
+
getAuthenticatedUser(config),
|
|
2097
|
+
getTask(config, issueNumber)
|
|
2098
|
+
]);
|
|
2099
|
+
spinner2.stop();
|
|
1926
2100
|
} catch (err) {
|
|
1927
|
-
|
|
2101
|
+
spinner2.stop();
|
|
1928
2102
|
return `Error: ${err.message}`;
|
|
1929
2103
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
var terminal6 = true;
|
|
1933
|
-
|
|
1934
|
-
// src/tools/refresh/index.ts
|
|
1935
|
-
var refresh_exports = {};
|
|
1936
|
-
__export(refresh_exports, {
|
|
1937
|
-
definition: () => definition7,
|
|
1938
|
-
execute: () => execute7,
|
|
1939
|
-
run: () => run7,
|
|
1940
|
-
terminal: () => terminal7
|
|
1941
|
-
});
|
|
1942
|
-
var definition7 = {
|
|
1943
|
-
type: "function",
|
|
1944
|
-
function: {
|
|
1945
|
-
name: "refresh",
|
|
1946
|
-
description: "Reload and display the full task list. Equivalent to /refresh.",
|
|
1947
|
-
parameters: { type: "object", properties: {}, required: [] }
|
|
2104
|
+
if (issue.author && issue.author !== me2) {
|
|
2105
|
+
return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
|
|
1948
2106
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
}
|
|
1961
|
-
var execute7 = run7;
|
|
1962
|
-
var terminal7 = true;
|
|
1963
|
-
|
|
1964
|
-
// src/tools/open-code/index.ts
|
|
1965
|
-
var open_code_exports = {};
|
|
1966
|
-
__export(open_code_exports, {
|
|
1967
|
-
definition: () => definition8,
|
|
1968
|
-
execute: () => execute8,
|
|
1969
|
-
run: () => run8,
|
|
1970
|
-
terminal: () => terminal8
|
|
1971
|
-
});
|
|
1972
|
-
init_github();
|
|
1973
|
-
var definition8 = {
|
|
1974
|
-
type: "function",
|
|
1975
|
-
function: {
|
|
1976
|
-
name: "open_code",
|
|
1977
|
-
description: "Launch Claude Code for the current task branch. Equivalent to /code.",
|
|
1978
|
-
parameters: { type: "object", properties: {}, required: [] }
|
|
2107
|
+
let confirmed;
|
|
2108
|
+
try {
|
|
2109
|
+
confirmed = await select5({
|
|
2110
|
+
message: `Merge PR for #${issueNumber} and close issue?`,
|
|
2111
|
+
choices: [
|
|
2112
|
+
{ name: "Yes, accept", value: true },
|
|
2113
|
+
{ name: "Cancel", value: false }
|
|
2114
|
+
]
|
|
2115
|
+
});
|
|
2116
|
+
} catch {
|
|
2117
|
+
return "Cancelled.";
|
|
1979
2118
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
let
|
|
2119
|
+
if (!confirmed) return "Cancelled.";
|
|
2120
|
+
const spinner = ora6(`Merging PR for #${issueNumber}\u2026`).start();
|
|
2121
|
+
let result;
|
|
1983
2122
|
try {
|
|
1984
|
-
|
|
2123
|
+
result = await acceptTask(config, issueNumber);
|
|
2124
|
+
spinner.succeed(`PR #${result.prNumber} merged \u2192 ${chalk7.cyan(result.baseBranch)}`);
|
|
1985
2125
|
} catch (err) {
|
|
2126
|
+
spinner.fail("Failed");
|
|
1986
2127
|
return `Error: ${err.message}`;
|
|
1987
2128
|
}
|
|
1988
|
-
const
|
|
1989
|
-
if (!
|
|
1990
|
-
|
|
1991
|
-
|
|
2129
|
+
const mergedIntoTaskBranch = isTaskBranch(result.baseBranch);
|
|
2130
|
+
if (!mergedIntoTaskBranch) {
|
|
2131
|
+
const baseBranch = config.baseBranch ?? "main";
|
|
2132
|
+
let pushToMain;
|
|
2133
|
+
try {
|
|
2134
|
+
pushToMain = await select5({
|
|
2135
|
+
message: `Push ${chalk7.cyan(result.baseBranch)} \u2192 ${chalk7.cyan(baseBranch)}?`,
|
|
2136
|
+
choices: [
|
|
2137
|
+
{ name: `Yes, push to ${baseBranch}`, value: true },
|
|
2138
|
+
{ name: "No, keep in worker branch", value: false }
|
|
2139
|
+
]
|
|
2140
|
+
});
|
|
2141
|
+
} catch {
|
|
2142
|
+
pushToMain = false;
|
|
2143
|
+
}
|
|
2144
|
+
if (pushToMain) {
|
|
2145
|
+
const mergeSpinner = ora6(`Merging ${result.baseBranch} \u2192 ${baseBranch}\u2026`).start();
|
|
2146
|
+
try {
|
|
2147
|
+
await mergeWorkerIntoBase(config, result.baseBranch, baseBranch);
|
|
2148
|
+
mergeSpinner.succeed(`Merged ${result.baseBranch} \u2192 ${baseBranch}`);
|
|
2149
|
+
} catch (err) {
|
|
2150
|
+
mergeSpinner.fail(`Could not merge to ${baseBranch}: ${err.message}`);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
let updateWiki = false;
|
|
1992
2155
|
try {
|
|
1993
|
-
|
|
2156
|
+
updateWiki = await select5({
|
|
2157
|
+
message: "Update TECHUNTER.md project overview?",
|
|
2158
|
+
choices: [
|
|
2159
|
+
{ name: "Yes, regenerate", value: true },
|
|
2160
|
+
{ name: "No, skip", value: false }
|
|
2161
|
+
]
|
|
2162
|
+
});
|
|
2163
|
+
} catch {
|
|
2164
|
+
}
|
|
2165
|
+
if (updateWiki) {
|
|
2166
|
+
const wikiSpinner = ora6("Regenerating TECHUNTER.md\u2026").start();
|
|
2167
|
+
try {
|
|
2168
|
+
const content = await generateWiki(config);
|
|
2169
|
+
await upsertRepoFile(config, "TECHUNTER.md", content, "docs: update TECHUNTER.md project overview");
|
|
2170
|
+
wikiSpinner.succeed("TECHUNTER.md updated");
|
|
2171
|
+
} catch (err) {
|
|
2172
|
+
wikiSpinner.fail(`Wiki update failed: ${err.message}`);
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
const mergeTarget = mergedIntoTaskBranch ? `${result.baseBranch} (sub-task merged, no push to main)` : result.baseBranch;
|
|
2176
|
+
return `Task #${issueNumber} accepted.
|
|
2177
|
+
PR #${result.prNumber} merged \u2192 ${mergeTarget}
|
|
2178
|
+
Issue closed.`;
|
|
2179
|
+
}
|
|
2180
|
+
async function execute6(input3, config) {
|
|
2181
|
+
const issueNumber = input3["issue_number"];
|
|
2182
|
+
const [me, issue] = await Promise.all([
|
|
2183
|
+
getAuthenticatedUser(config),
|
|
2184
|
+
getTask(config, issueNumber)
|
|
2185
|
+
]);
|
|
2186
|
+
if (issue.author && issue.author !== me) {
|
|
2187
|
+
return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
|
|
2188
|
+
}
|
|
2189
|
+
const spinner = ora6(`Merging PR for #${issueNumber}\u2026`).start();
|
|
2190
|
+
try {
|
|
2191
|
+
const result = await acceptTask(config, issueNumber);
|
|
2192
|
+
spinner.stop();
|
|
2193
|
+
return `Task #${issueNumber} accepted.
|
|
2194
|
+
PR #${result.prNumber} merged \u2192 ${result.baseBranch}
|
|
2195
|
+
Issue closed.`;
|
|
1994
2196
|
} catch (err) {
|
|
2197
|
+
spinner.stop();
|
|
1995
2198
|
return `Error: ${err.message}`;
|
|
1996
2199
|
}
|
|
1997
|
-
await launchClaudeCode(issue, branch);
|
|
1998
|
-
return "Claude Code session ended.";
|
|
1999
2200
|
}
|
|
2000
|
-
var
|
|
2001
|
-
var terminal8 = true;
|
|
2201
|
+
var terminal6 = true;
|
|
2002
2202
|
|
|
2003
2203
|
// src/tools/reject/index.ts
|
|
2004
2204
|
var reject_exports = {};
|
|
2005
2205
|
__export(reject_exports, {
|
|
2006
|
-
definition: () =>
|
|
2007
|
-
execute: () =>
|
|
2008
|
-
run: () =>
|
|
2009
|
-
terminal: () =>
|
|
2206
|
+
definition: () => definition7,
|
|
2207
|
+
execute: () => execute7,
|
|
2208
|
+
run: () => run7,
|
|
2209
|
+
terminal: () => terminal7
|
|
2010
2210
|
});
|
|
2011
2211
|
init_github();
|
|
2012
|
-
import
|
|
2013
|
-
import { select as
|
|
2014
|
-
import
|
|
2212
|
+
import chalk8 from "chalk";
|
|
2213
|
+
import { select as select6, input as promptInput3 } from "@inquirer/prompts";
|
|
2214
|
+
import ora7 from "ora";
|
|
2015
2215
|
|
|
2016
2216
|
// src/tools/reject/prompts.ts
|
|
2017
2217
|
var REJECTION_FORMAT = `
|
|
@@ -2042,7 +2242,7 @@ Reviewer feedback: ${userFeedback}`,
|
|
|
2042
2242
|
}
|
|
2043
2243
|
|
|
2044
2244
|
// src/tools/reject/index.ts
|
|
2045
|
-
var
|
|
2245
|
+
var definition7 = {
|
|
2046
2246
|
type: "function",
|
|
2047
2247
|
function: {
|
|
2048
2248
|
name: "reject",
|
|
@@ -2057,7 +2257,7 @@ var definition9 = {
|
|
|
2057
2257
|
}
|
|
2058
2258
|
}
|
|
2059
2259
|
};
|
|
2060
|
-
async function
|
|
2260
|
+
async function run7(input3, config) {
|
|
2061
2261
|
const issueNumber = input3["issue_number"];
|
|
2062
2262
|
const [me, issue] = await Promise.all([
|
|
2063
2263
|
getAuthenticatedUser(config),
|
|
@@ -2075,9 +2275,9 @@ async function run9(input3, config) {
|
|
|
2075
2275
|
return "Cancelled.";
|
|
2076
2276
|
}
|
|
2077
2277
|
if (!feedback.trim()) return "Cancelled.";
|
|
2078
|
-
const divider =
|
|
2278
|
+
const divider = chalk8.dim("\u2500".repeat(70));
|
|
2079
2279
|
for (; ; ) {
|
|
2080
|
-
const spinner =
|
|
2280
|
+
const spinner = ora7("Generating rejection comment\u2026").start();
|
|
2081
2281
|
let comment;
|
|
2082
2282
|
try {
|
|
2083
2283
|
comment = await generateRejectionComment(config, issueNumber, feedback);
|
|
@@ -2087,13 +2287,13 @@ async function run9(input3, config) {
|
|
|
2087
2287
|
return `Error generating comment: ${err.message}`;
|
|
2088
2288
|
}
|
|
2089
2289
|
console.log("\n" + divider);
|
|
2090
|
-
console.log(
|
|
2290
|
+
console.log(chalk8.bold(` Rejection preview \u2014 issue #${issueNumber}`));
|
|
2091
2291
|
console.log(divider);
|
|
2092
2292
|
console.log(renderMarkdown(comment));
|
|
2093
2293
|
console.log(divider + "\n");
|
|
2094
2294
|
let decision;
|
|
2095
2295
|
try {
|
|
2096
|
-
decision = await
|
|
2296
|
+
decision = await select6({
|
|
2097
2297
|
message: `Post rejection and mark #${issueNumber} as changes-needed?`,
|
|
2098
2298
|
choices: [
|
|
2099
2299
|
{ name: "Post & Reject", value: "yes" },
|
|
@@ -2113,7 +2313,7 @@ async function run9(input3, config) {
|
|
|
2113
2313
|
}
|
|
2114
2314
|
continue;
|
|
2115
2315
|
}
|
|
2116
|
-
let spinner2 =
|
|
2316
|
+
let spinner2 = ora7(`Posting rejection comment on #${issueNumber}\u2026`).start();
|
|
2117
2317
|
try {
|
|
2118
2318
|
await postComment(config, issueNumber, comment);
|
|
2119
2319
|
spinner2.stop();
|
|
@@ -2121,7 +2321,7 @@ async function run9(input3, config) {
|
|
|
2121
2321
|
spinner2.stop();
|
|
2122
2322
|
return `Error posting comment: ${err.message}`;
|
|
2123
2323
|
}
|
|
2124
|
-
spinner2 =
|
|
2324
|
+
spinner2 = ora7(`Marking #${issueNumber} as changes-needed\u2026`).start();
|
|
2125
2325
|
try {
|
|
2126
2326
|
await rejectTask(config, issueNumber);
|
|
2127
2327
|
spinner2.stop();
|
|
@@ -2132,7 +2332,7 @@ async function run9(input3, config) {
|
|
|
2132
2332
|
return `Task #${issueNumber} rejected. Label changed to changes-needed.`;
|
|
2133
2333
|
}
|
|
2134
2334
|
}
|
|
2135
|
-
async function
|
|
2335
|
+
async function execute7(input3, config) {
|
|
2136
2336
|
const issueNumber = input3["issue_number"];
|
|
2137
2337
|
const feedback = input3["feedback"];
|
|
2138
2338
|
const [me, issue] = await Promise.all([
|
|
@@ -2163,255 +2363,536 @@ async function execute9(input3, config) {
|
|
|
2163
2363
|
Comment posted:
|
|
2164
2364
|
${comment}`;
|
|
2165
2365
|
}
|
|
2366
|
+
var terminal7 = true;
|
|
2367
|
+
|
|
2368
|
+
// src/tools/review/index.ts
|
|
2369
|
+
var definition8 = {
|
|
2370
|
+
type: "function",
|
|
2371
|
+
function: {
|
|
2372
|
+
name: "review",
|
|
2373
|
+
description: "List tasks waiting for your review (submitted by others, created by you), then let you accept or reject one. Equivalent to /review.",
|
|
2374
|
+
parameters: { type: "object", properties: {}, required: [] }
|
|
2375
|
+
}
|
|
2376
|
+
};
|
|
2377
|
+
async function run8(_input, config) {
|
|
2378
|
+
const spinner = ora8("Loading tasks for review\u2026").start();
|
|
2379
|
+
let me;
|
|
2380
|
+
let tasks;
|
|
2381
|
+
try {
|
|
2382
|
+
me = await getAuthenticatedUser(config);
|
|
2383
|
+
tasks = await listTasksForReview(config, me);
|
|
2384
|
+
spinner.stop();
|
|
2385
|
+
} catch (err) {
|
|
2386
|
+
spinner.stop();
|
|
2387
|
+
return `Error: ${err.message}`;
|
|
2388
|
+
}
|
|
2389
|
+
if (tasks.length === 0) return `No tasks pending review for @${me}.`;
|
|
2390
|
+
let issueNumber;
|
|
2391
|
+
try {
|
|
2392
|
+
issueNumber = await select7({
|
|
2393
|
+
message: "Select a task to review:",
|
|
2394
|
+
choices: tasks.map((t) => ({
|
|
2395
|
+
name: `#${String(t.number).padEnd(4)} @${t.assignee ?? "\u2014"} ${t.title}`,
|
|
2396
|
+
value: t.number
|
|
2397
|
+
}))
|
|
2398
|
+
});
|
|
2399
|
+
} catch {
|
|
2400
|
+
return "Cancelled.";
|
|
2401
|
+
}
|
|
2402
|
+
const spinner2 = ora8(`Loading #${issueNumber}\u2026`).start();
|
|
2403
|
+
let pr;
|
|
2404
|
+
try {
|
|
2405
|
+
pr = await getTaskPR(config, issueNumber);
|
|
2406
|
+
spinner2.stop();
|
|
2407
|
+
} catch (err) {
|
|
2408
|
+
spinner2.stop();
|
|
2409
|
+
return `Error loading PR: ${err.message}`;
|
|
2410
|
+
}
|
|
2411
|
+
const divider = chalk9.dim("\u2500".repeat(70));
|
|
2412
|
+
console.log("\n" + divider);
|
|
2413
|
+
if (pr) {
|
|
2414
|
+
console.log(chalk9.bold(` PR #${pr.number}`) + " " + chalk9.dim(pr.url));
|
|
2415
|
+
console.log(divider);
|
|
2416
|
+
console.log(renderMarkdown(pr.body));
|
|
2417
|
+
} else {
|
|
2418
|
+
console.log(chalk9.yellow(` No open PR found for task #${issueNumber}`));
|
|
2419
|
+
}
|
|
2420
|
+
console.log(divider + "\n");
|
|
2421
|
+
for (; ; ) {
|
|
2422
|
+
let action;
|
|
2423
|
+
try {
|
|
2424
|
+
action = await select7({
|
|
2425
|
+
message: "Review action:",
|
|
2426
|
+
choices: [
|
|
2427
|
+
...pr ? [{ name: "View diff", value: "diff" }] : [],
|
|
2428
|
+
{ name: chalk9.green("Accept") + " \u2014 merge PR and close issue", value: "accept" },
|
|
2429
|
+
{ name: chalk9.red("Reject") + " \u2014 request changes", value: "reject" },
|
|
2430
|
+
{ name: "Nothing, just viewing", value: "none" }
|
|
2431
|
+
]
|
|
2432
|
+
});
|
|
2433
|
+
} catch {
|
|
2434
|
+
return "Cancelled.";
|
|
2435
|
+
}
|
|
2436
|
+
if (action === "none") return `Viewed task #${issueNumber}.`;
|
|
2437
|
+
if (action === "accept") return run6({ issue_number: issueNumber }, config);
|
|
2438
|
+
if (action === "reject") return run7({ issue_number: issueNumber }, config);
|
|
2439
|
+
if (action === "diff") {
|
|
2440
|
+
const diffSpinner = ora8("Fetching diff\u2026").start();
|
|
2441
|
+
let diff;
|
|
2442
|
+
try {
|
|
2443
|
+
diff = await getTaskPRDiff(config, pr.number);
|
|
2444
|
+
diffSpinner.stop();
|
|
2445
|
+
} catch (err) {
|
|
2446
|
+
diffSpinner.stop();
|
|
2447
|
+
console.log(chalk9.red(`Error fetching diff: ${err.message}`));
|
|
2448
|
+
continue;
|
|
2449
|
+
}
|
|
2450
|
+
console.log("\n" + divider);
|
|
2451
|
+
for (const line of diff.split("\n")) {
|
|
2452
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
2453
|
+
process.stdout.write(chalk9.green(line) + "\n");
|
|
2454
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
2455
|
+
process.stdout.write(chalk9.red(line) + "\n");
|
|
2456
|
+
} else if (line.startsWith("@@")) {
|
|
2457
|
+
process.stdout.write(chalk9.cyan(line) + "\n");
|
|
2458
|
+
} else if (line.startsWith("diff ") || line.startsWith("index ") || line.startsWith("+++") || line.startsWith("---")) {
|
|
2459
|
+
process.stdout.write(chalk9.bold(line) + "\n");
|
|
2460
|
+
} else {
|
|
2461
|
+
process.stdout.write(line + "\n");
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
console.log(divider + "\n");
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
var execute8 = run8;
|
|
2469
|
+
var terminal8 = true;
|
|
2470
|
+
|
|
2471
|
+
// src/tools/refresh/index.ts
|
|
2472
|
+
var refresh_exports = {};
|
|
2473
|
+
__export(refresh_exports, {
|
|
2474
|
+
definition: () => definition9,
|
|
2475
|
+
execute: () => execute9,
|
|
2476
|
+
run: () => run9,
|
|
2477
|
+
terminal: () => terminal9
|
|
2478
|
+
});
|
|
2479
|
+
var definition9 = {
|
|
2480
|
+
type: "function",
|
|
2481
|
+
function: {
|
|
2482
|
+
name: "refresh",
|
|
2483
|
+
description: "Reload and display the full task list. Equivalent to /refresh.",
|
|
2484
|
+
parameters: { type: "object", properties: {}, required: [] }
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
async function run9(_input, config) {
|
|
2488
|
+
const tasks = await printTaskList(config);
|
|
2489
|
+
if (tasks.length === 0) return "No tasks found.";
|
|
2490
|
+
const lines = tasks.map((t) => {
|
|
2491
|
+
const status = getStatus(t);
|
|
2492
|
+
const assignee = t.assignee ? `@${t.assignee}` : "\u2014";
|
|
2493
|
+
return `#${t.number} [${status}] ${assignee} ${t.title}`;
|
|
2494
|
+
});
|
|
2495
|
+
return `Tasks (${tasks.length}):
|
|
2496
|
+
${lines.join("\n")}`;
|
|
2497
|
+
}
|
|
2498
|
+
var execute9 = run9;
|
|
2166
2499
|
var terminal9 = true;
|
|
2167
2500
|
|
|
2168
|
-
// src/tools/
|
|
2169
|
-
var
|
|
2170
|
-
__export(
|
|
2501
|
+
// src/tools/open-code/index.ts
|
|
2502
|
+
var open_code_exports = {};
|
|
2503
|
+
__export(open_code_exports, {
|
|
2171
2504
|
definition: () => definition10,
|
|
2172
2505
|
execute: () => execute10,
|
|
2173
2506
|
run: () => run10,
|
|
2174
2507
|
terminal: () => terminal10
|
|
2175
2508
|
});
|
|
2176
2509
|
init_github();
|
|
2177
|
-
import chalk11 from "chalk";
|
|
2178
|
-
import { select as select8 } from "@inquirer/prompts";
|
|
2179
|
-
import ora9 from "ora";
|
|
2180
2510
|
var definition10 = {
|
|
2181
2511
|
type: "function",
|
|
2182
2512
|
function: {
|
|
2183
|
-
name: "
|
|
2184
|
-
description: "
|
|
2513
|
+
name: "open_code",
|
|
2514
|
+
description: "Launch Claude Code for the current task branch. Equivalent to /code.",
|
|
2515
|
+
parameters: { type: "object", properties: {}, required: [] }
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
async function run10(_input, config) {
|
|
2519
|
+
let branch;
|
|
2520
|
+
try {
|
|
2521
|
+
branch = await getCurrentBranch();
|
|
2522
|
+
} catch (err) {
|
|
2523
|
+
return `Error: ${err.message}`;
|
|
2524
|
+
}
|
|
2525
|
+
let issueNum = getConfig().taskState?.activeIssueNumber;
|
|
2526
|
+
if (!issueNum) {
|
|
2527
|
+
const found = await getIssueNumberFromBranch(config, branch);
|
|
2528
|
+
if (!found) return `No active task found (current branch: ${branch}).`;
|
|
2529
|
+
issueNum = found.issueNumber;
|
|
2530
|
+
}
|
|
2531
|
+
let issue;
|
|
2532
|
+
try {
|
|
2533
|
+
issue = await getTask(config, issueNum);
|
|
2534
|
+
} catch (err) {
|
|
2535
|
+
return `Error: ${err.message}`;
|
|
2536
|
+
}
|
|
2537
|
+
await launchClaudeCode(issue, branch);
|
|
2538
|
+
return "Claude Code session ended.";
|
|
2539
|
+
}
|
|
2540
|
+
var execute10 = run10;
|
|
2541
|
+
var terminal10 = true;
|
|
2542
|
+
|
|
2543
|
+
// src/tools/edit-task/index.ts
|
|
2544
|
+
var edit_task_exports = {};
|
|
2545
|
+
__export(edit_task_exports, {
|
|
2546
|
+
definition: () => definition11,
|
|
2547
|
+
execute: () => execute11,
|
|
2548
|
+
run: () => run11,
|
|
2549
|
+
terminal: () => terminal11
|
|
2550
|
+
});
|
|
2551
|
+
init_github();
|
|
2552
|
+
import { select as select8, input as promptInput4 } from "@inquirer/prompts";
|
|
2553
|
+
import ora9 from "ora";
|
|
2554
|
+
var definition11 = {
|
|
2555
|
+
type: "function",
|
|
2556
|
+
function: {
|
|
2557
|
+
name: "edit_task",
|
|
2558
|
+
description: "Edit the title and/or body of an existing task (GitHub Issue). Equivalent to /edit.",
|
|
2185
2559
|
parameters: {
|
|
2186
2560
|
type: "object",
|
|
2187
2561
|
properties: {
|
|
2188
|
-
issue_number: { type: "number", description: "
|
|
2562
|
+
issue_number: { type: "number", description: "Issue number to edit." },
|
|
2563
|
+
title: { type: "string", description: "New title." },
|
|
2564
|
+
body: { type: "string", description: "New body/description." }
|
|
2189
2565
|
},
|
|
2190
|
-
required: ["issue_number"]
|
|
2566
|
+
required: ["issue_number", "title", "body"]
|
|
2191
2567
|
}
|
|
2192
2568
|
}
|
|
2193
2569
|
};
|
|
2194
|
-
async function
|
|
2570
|
+
async function run11(input3, config) {
|
|
2195
2571
|
let issueNumber = input3["issue_number"];
|
|
2196
2572
|
if (!issueNumber) {
|
|
2197
|
-
const spinner3 = ora9("Loading tasks for review\u2026").start();
|
|
2198
2573
|
let tasks;
|
|
2199
|
-
let me;
|
|
2200
2574
|
try {
|
|
2201
|
-
|
|
2202
|
-
tasks = await listTasksForReview(config, me);
|
|
2203
|
-
spinner3.stop();
|
|
2575
|
+
tasks = await listTasks(config);
|
|
2204
2576
|
} catch (err) {
|
|
2205
|
-
|
|
2206
|
-
return `Error: ${err.message}`;
|
|
2577
|
+
return `Error loading tasks: ${err.message}`;
|
|
2207
2578
|
}
|
|
2208
|
-
if (tasks.length === 0) return "No tasks
|
|
2579
|
+
if (tasks.length === 0) return "No tasks found.";
|
|
2209
2580
|
try {
|
|
2210
2581
|
issueNumber = await select8({
|
|
2211
|
-
message: "
|
|
2212
|
-
choices: tasks.map((t) => ({
|
|
2213
|
-
name: `#${t.number} @${t.assignee ?? "\u2014"} ${t.title}`,
|
|
2214
|
-
value: t.number
|
|
2215
|
-
}))
|
|
2582
|
+
message: "Select task to edit:",
|
|
2583
|
+
choices: tasks.map((t) => ({ name: `#${t.number} [${getStatus(t)}] ${t.title}`, value: t.number }))
|
|
2216
2584
|
});
|
|
2217
2585
|
} catch {
|
|
2218
2586
|
return "Cancelled.";
|
|
2219
2587
|
}
|
|
2220
2588
|
}
|
|
2221
|
-
const spinner2 = ora9("Verifying permissions\u2026").start();
|
|
2222
|
-
let me2;
|
|
2223
2589
|
let issue;
|
|
2224
2590
|
try {
|
|
2225
|
-
|
|
2226
|
-
getAuthenticatedUser(config),
|
|
2227
|
-
getTask(config, issueNumber)
|
|
2228
|
-
]);
|
|
2229
|
-
spinner2.stop();
|
|
2591
|
+
issue = await getTask(config, issueNumber);
|
|
2230
2592
|
} catch (err) {
|
|
2231
|
-
|
|
2232
|
-
return `Error: ${err.message}`;
|
|
2233
|
-
}
|
|
2234
|
-
if (issue.author && issue.author !== me2) {
|
|
2235
|
-
return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
|
|
2593
|
+
return `Error loading task: ${err.message}`;
|
|
2236
2594
|
}
|
|
2237
|
-
|
|
2238
|
-
let
|
|
2595
|
+
let title;
|
|
2596
|
+
let body;
|
|
2239
2597
|
try {
|
|
2240
|
-
|
|
2241
|
-
message:
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2598
|
+
title = await promptInput4({
|
|
2599
|
+
message: "Title:",
|
|
2600
|
+
default: issue.title
|
|
2601
|
+
});
|
|
2602
|
+
body = await promptInput4({
|
|
2603
|
+
message: "Description:",
|
|
2604
|
+
default: issue.body ?? ""
|
|
2246
2605
|
});
|
|
2247
2606
|
} catch {
|
|
2248
2607
|
return "Cancelled.";
|
|
2249
2608
|
}
|
|
2250
|
-
if (
|
|
2251
|
-
|
|
2252
|
-
|
|
2609
|
+
if (title.trim() === issue.title && body.trim() === (issue.body ?? "")) {
|
|
2610
|
+
return "No changes made.";
|
|
2611
|
+
}
|
|
2612
|
+
const spinner = ora9(`Updating #${issueNumber}\u2026`).start();
|
|
2253
2613
|
try {
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2614
|
+
await editTask(config, issueNumber, title.trim() || issue.title, body.trim());
|
|
2615
|
+
spinner.stop();
|
|
2616
|
+
return `Task #${issueNumber} updated.`;
|
|
2257
2617
|
} catch (err) {
|
|
2258
|
-
spinner.
|
|
2618
|
+
spinner.stop();
|
|
2259
2619
|
return `Error: ${err.message}`;
|
|
2260
2620
|
}
|
|
2261
|
-
const baseBranch = config.baseBranch ?? "main";
|
|
2262
|
-
let pushToMain;
|
|
2263
|
-
try {
|
|
2264
|
-
pushToMain = await select8({
|
|
2265
|
-
message: `Push ${chalk11.cyan(targetBranch)} \u2192 ${chalk11.cyan(baseBranch)}?`,
|
|
2266
|
-
choices: [
|
|
2267
|
-
{ name: `Yes, push to ${baseBranch}`, value: true },
|
|
2268
|
-
{ name: "No, keep in worker branch", value: false }
|
|
2269
|
-
]
|
|
2270
|
-
});
|
|
2271
|
-
} catch {
|
|
2272
|
-
pushToMain = false;
|
|
2273
|
-
}
|
|
2274
|
-
if (pushToMain) {
|
|
2275
|
-
const mergeSpinner = ora9(`Merging ${targetBranch} \u2192 ${baseBranch}\u2026`).start();
|
|
2276
|
-
try {
|
|
2277
|
-
await mergeWorkerIntoBase(config, targetBranch, baseBranch);
|
|
2278
|
-
mergeSpinner.succeed(`Merged ${targetBranch} \u2192 ${baseBranch}`);
|
|
2279
|
-
} catch (err) {
|
|
2280
|
-
mergeSpinner.fail(`Could not merge to ${baseBranch}: ${err.message}`);
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
return `Task #${issueNumber} accepted.
|
|
2284
|
-
PR #${result.prNumber} merged \u2192 ${targetBranch}${pushToMain ? ` \u2192 ${baseBranch}` : ""}
|
|
2285
|
-
Issue closed.`;
|
|
2286
2621
|
}
|
|
2287
|
-
async function
|
|
2622
|
+
async function execute11(input3, config) {
|
|
2288
2623
|
const issueNumber = input3["issue_number"];
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
]);
|
|
2293
|
-
if (issue.author && issue.author !== me) {
|
|
2294
|
-
return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
|
|
2295
|
-
}
|
|
2296
|
-
const targetBranch = makeWorkerBranchName(me);
|
|
2297
|
-
const spinner = ora9(`Merging PR for #${issueNumber}\u2026`).start();
|
|
2624
|
+
const title = input3["title"];
|
|
2625
|
+
const body = input3["body"];
|
|
2626
|
+
const spinner = ora9(`Updating #${issueNumber}\u2026`).start();
|
|
2298
2627
|
try {
|
|
2299
|
-
|
|
2300
|
-
const result = await acceptTask(config, issueNumber, assigneeWorkerBranch);
|
|
2628
|
+
await editTask(config, issueNumber, title, body);
|
|
2301
2629
|
spinner.stop();
|
|
2302
|
-
return `Task #${issueNumber}
|
|
2303
|
-
PR #${result.prNumber} merged \u2192 ${targetBranch}
|
|
2304
|
-
Issue closed.`;
|
|
2630
|
+
return `Task #${issueNumber} updated.`;
|
|
2305
2631
|
} catch (err) {
|
|
2306
2632
|
spinner.stop();
|
|
2307
2633
|
return `Error: ${err.message}`;
|
|
2308
2634
|
}
|
|
2309
2635
|
}
|
|
2310
|
-
var
|
|
2636
|
+
var terminal11 = true;
|
|
2311
2637
|
|
|
2312
|
-
// src/tools/
|
|
2313
|
-
var
|
|
2314
|
-
__export(
|
|
2315
|
-
definition: () =>
|
|
2316
|
-
execute: () =>
|
|
2317
|
-
run: () =>
|
|
2318
|
-
terminal: () =>
|
|
2638
|
+
// src/tools/move-task/index.ts
|
|
2639
|
+
var move_task_exports = {};
|
|
2640
|
+
__export(move_task_exports, {
|
|
2641
|
+
definition: () => definition12,
|
|
2642
|
+
execute: () => execute12,
|
|
2643
|
+
run: () => run12,
|
|
2644
|
+
terminal: () => terminal12
|
|
2319
2645
|
});
|
|
2320
2646
|
init_github();
|
|
2321
|
-
import { select as select9
|
|
2647
|
+
import { select as select9 } from "@inquirer/prompts";
|
|
2322
2648
|
import ora10 from "ora";
|
|
2323
|
-
|
|
2649
|
+
import chalk10 from "chalk";
|
|
2650
|
+
var definition12 = {
|
|
2324
2651
|
type: "function",
|
|
2325
2652
|
function: {
|
|
2326
|
-
name: "
|
|
2327
|
-
description: "
|
|
2653
|
+
name: "move_task",
|
|
2654
|
+
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.",
|
|
2328
2655
|
parameters: {
|
|
2329
2656
|
type: "object",
|
|
2330
2657
|
properties: {
|
|
2331
|
-
issue_number: { type: "number", description: "Issue number to
|
|
2332
|
-
|
|
2333
|
-
body: { type: "string", description: "New body/description." }
|
|
2658
|
+
issue_number: { type: "number", description: "Issue number of the task to move." },
|
|
2659
|
+
parent_issue_number: { type: "number", description: "Issue number of the new parent task." }
|
|
2334
2660
|
},
|
|
2335
|
-
required: ["issue_number", "
|
|
2661
|
+
required: ["issue_number", "parent_issue_number"]
|
|
2336
2662
|
}
|
|
2337
2663
|
}
|
|
2338
2664
|
};
|
|
2339
|
-
async function
|
|
2665
|
+
async function run12(input3, config) {
|
|
2666
|
+
const me = await getAuthenticatedUser(config);
|
|
2667
|
+
const allTasks = await listTasks(config);
|
|
2340
2668
|
let issueNumber = input3["issue_number"];
|
|
2341
|
-
|
|
2342
|
-
|
|
2669
|
+
let taskToMove;
|
|
2670
|
+
if (issueNumber) {
|
|
2343
2671
|
try {
|
|
2344
|
-
|
|
2672
|
+
taskToMove = await getTask(config, issueNumber);
|
|
2345
2673
|
} catch (err) {
|
|
2346
|
-
return `Error loading
|
|
2674
|
+
return `Error loading task #${issueNumber}: ${err.message}`;
|
|
2347
2675
|
}
|
|
2348
|
-
if (
|
|
2676
|
+
if (taskToMove.author !== me) {
|
|
2677
|
+
return `Task #${issueNumber} was not authored by you \u2014 you can only move your own tasks.`;
|
|
2678
|
+
}
|
|
2679
|
+
} else {
|
|
2680
|
+
const myTasks = allTasks.filter((t) => t.author === me);
|
|
2681
|
+
if (myTasks.length === 0) return "No tasks you authored are available to move.";
|
|
2349
2682
|
try {
|
|
2350
2683
|
issueNumber = await select9({
|
|
2351
|
-
message: "Select task to
|
|
2352
|
-
choices:
|
|
2684
|
+
message: "Select task to move:",
|
|
2685
|
+
choices: myTasks.map((t) => ({
|
|
2686
|
+
name: `#${t.number} [${getStatus(t)}] ${t.title}`,
|
|
2687
|
+
value: t.number
|
|
2688
|
+
}))
|
|
2353
2689
|
});
|
|
2354
2690
|
} catch {
|
|
2355
2691
|
return "Cancelled.";
|
|
2356
2692
|
}
|
|
2693
|
+
taskToMove = myTasks.find((t) => t.number === issueNumber);
|
|
2694
|
+
}
|
|
2695
|
+
const candidates = allTasks.filter((t) => t.number !== taskToMove.number);
|
|
2696
|
+
const resolveSpinner = ora10("Finding parent task branches\u2026").start();
|
|
2697
|
+
const parents = [];
|
|
2698
|
+
for (const t of candidates) {
|
|
2699
|
+
const branch = await getTaskBranch(config, t.number);
|
|
2700
|
+
if (branch) parents.push({ task: t, branch });
|
|
2701
|
+
}
|
|
2702
|
+
resolveSpinner.stop();
|
|
2703
|
+
if (parents.length === 0) {
|
|
2704
|
+
return "No other tasks with known branches are available as a parent.";
|
|
2705
|
+
}
|
|
2706
|
+
let parentIssueNumber = input3["parent_issue_number"];
|
|
2707
|
+
let chosen;
|
|
2708
|
+
if (parentIssueNumber) {
|
|
2709
|
+
const found = parents.find((p) => p.task.number === parentIssueNumber);
|
|
2710
|
+
if (!found) {
|
|
2711
|
+
return `Task #${parentIssueNumber} is not available as a parent (no branch found or not open).`;
|
|
2712
|
+
}
|
|
2713
|
+
chosen = found;
|
|
2714
|
+
} else {
|
|
2715
|
+
try {
|
|
2716
|
+
const selectedBranch = await select9({
|
|
2717
|
+
message: `Move #${taskToMove.number} under which task?`,
|
|
2718
|
+
choices: parents.map((p) => ({
|
|
2719
|
+
name: `#${p.task.number} [${getStatus(p.task)}] ${p.task.title} ${chalk10.dim("\u2192 " + p.branch)}`,
|
|
2720
|
+
value: p.branch
|
|
2721
|
+
}))
|
|
2722
|
+
});
|
|
2723
|
+
chosen = parents.find((p) => p.branch === selectedBranch);
|
|
2724
|
+
} catch {
|
|
2725
|
+
return "Cancelled.";
|
|
2726
|
+
}
|
|
2357
2727
|
}
|
|
2358
|
-
|
|
2728
|
+
const sha = await getBranchHeadSha(config, chosen.branch);
|
|
2729
|
+
if (!sha) {
|
|
2730
|
+
return `Could not resolve HEAD of branch ${chosen.branch} \u2014 does it exist on the remote?`;
|
|
2731
|
+
}
|
|
2732
|
+
const spinner = ora10(`Moving #${taskToMove.number} under #${chosen.task.number}\u2026`).start();
|
|
2359
2733
|
try {
|
|
2360
|
-
|
|
2734
|
+
await moveTask(config, taskToMove.number, chosen.branch, sha);
|
|
2735
|
+
spinner.succeed(
|
|
2736
|
+
`Task #${taskToMove.number} moved under #${chosen.task.number} "${chosen.task.title}"
|
|
2737
|
+
target: ${chalk10.cyan(chosen.branch)} base: ${chalk10.dim(sha.slice(0, 7))}`
|
|
2738
|
+
);
|
|
2739
|
+
return `Task #${taskToMove.number} moved under #${chosen.task.number} (branch: ${chosen.branch}, base: ${sha.slice(0, 7)})`;
|
|
2361
2740
|
} catch (err) {
|
|
2362
|
-
|
|
2741
|
+
spinner.fail(`Failed: ${err.message}`);
|
|
2742
|
+
return `Error: ${err.message}`;
|
|
2363
2743
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2744
|
+
}
|
|
2745
|
+
async function execute12(input3, config) {
|
|
2746
|
+
const me = await getAuthenticatedUser(config);
|
|
2747
|
+
const issueNumber = input3["issue_number"];
|
|
2748
|
+
const parentIssueNumber = input3["parent_issue_number"];
|
|
2749
|
+
const task = await getTask(config, issueNumber);
|
|
2750
|
+
if (task.author !== me) {
|
|
2751
|
+
return `Task #${issueNumber} was not authored by you \u2014 you can only move your own tasks.`;
|
|
2752
|
+
}
|
|
2753
|
+
const branch = await getTaskBranch(config, parentIssueNumber);
|
|
2754
|
+
if (!branch) return `No branch found for parent task #${parentIssueNumber}.`;
|
|
2755
|
+
const sha = await getBranchHeadSha(config, branch);
|
|
2756
|
+
if (!sha) return `Could not resolve HEAD of branch ${branch}.`;
|
|
2757
|
+
await moveTask(config, issueNumber, branch, sha);
|
|
2758
|
+
return `Task #${issueNumber} moved under #${parentIssueNumber} (branch: ${branch}, base: ${sha.slice(0, 7)})`;
|
|
2759
|
+
}
|
|
2760
|
+
var terminal12 = true;
|
|
2761
|
+
|
|
2762
|
+
// src/tools/wiki/index.ts
|
|
2763
|
+
var wiki_exports = {};
|
|
2764
|
+
__export(wiki_exports, {
|
|
2765
|
+
definition: () => definition13,
|
|
2766
|
+
execute: () => execute13,
|
|
2767
|
+
run: () => run13,
|
|
2768
|
+
terminal: () => terminal13
|
|
2769
|
+
});
|
|
2770
|
+
import ora11 from "ora";
|
|
2771
|
+
import chalk11 from "chalk";
|
|
2772
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2773
|
+
import { select as select10 } from "@inquirer/prompts";
|
|
2774
|
+
init_github();
|
|
2775
|
+
var WIKI_PATH = "TECHUNTER.md";
|
|
2776
|
+
var definition13 = {
|
|
2777
|
+
type: "function",
|
|
2778
|
+
function: {
|
|
2779
|
+
name: "update_wiki",
|
|
2780
|
+
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.",
|
|
2781
|
+
parameters: {
|
|
2782
|
+
type: "object",
|
|
2783
|
+
properties: {},
|
|
2784
|
+
required: []
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
};
|
|
2788
|
+
async function readWikiContent(config) {
|
|
2366
2789
|
try {
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2790
|
+
return await readFile2(WIKI_PATH, "utf-8");
|
|
2791
|
+
} catch {
|
|
2792
|
+
}
|
|
2793
|
+
return getRepoFile(config, WIKI_PATH);
|
|
2794
|
+
}
|
|
2795
|
+
function printWiki(content) {
|
|
2796
|
+
const divider = chalk11.dim("\u2500".repeat(70));
|
|
2797
|
+
console.log("\n" + divider);
|
|
2798
|
+
console.log(chalk11.bold(" TECHUNTER.md"));
|
|
2799
|
+
console.log(divider);
|
|
2800
|
+
console.log(renderMarkdown(content));
|
|
2801
|
+
console.log(divider + "\n");
|
|
2802
|
+
}
|
|
2803
|
+
async function run13(_input, config) {
|
|
2804
|
+
const fetchSpinner = ora11("Checking for existing wiki\u2026").start();
|
|
2805
|
+
const existing = await readWikiContent(config).catch(() => null);
|
|
2806
|
+
fetchSpinner.stop();
|
|
2807
|
+
let action;
|
|
2808
|
+
try {
|
|
2809
|
+
action = await select10({
|
|
2810
|
+
message: "TECHUNTER.md \u2014 what would you like to do?",
|
|
2811
|
+
choices: [
|
|
2812
|
+
...existing ? [{ name: "View current wiki", value: "view" }] : [],
|
|
2813
|
+
{ name: existing ? "Regenerate & commit to repo" : "Generate & commit to repo", value: "generate" },
|
|
2814
|
+
{ name: "Cancel", value: "cancel" }
|
|
2815
|
+
]
|
|
2374
2816
|
});
|
|
2375
2817
|
} catch {
|
|
2376
2818
|
return "Cancelled.";
|
|
2377
2819
|
}
|
|
2378
|
-
if (
|
|
2379
|
-
|
|
2820
|
+
if (action === "cancel") return "Cancelled.";
|
|
2821
|
+
if (action === "view") {
|
|
2822
|
+
printWiki(existing);
|
|
2823
|
+
return "Displayed TECHUNTER.md.";
|
|
2380
2824
|
}
|
|
2381
|
-
const
|
|
2825
|
+
const authSpinner = ora11("Checking permissions\u2026").start();
|
|
2826
|
+
let me;
|
|
2827
|
+
let allowed;
|
|
2382
2828
|
try {
|
|
2383
|
-
await
|
|
2384
|
-
|
|
2385
|
-
|
|
2829
|
+
me = await getAuthenticatedUser(config);
|
|
2830
|
+
allowed = await isCollaborator(config, me);
|
|
2831
|
+
authSpinner.stop();
|
|
2386
2832
|
} catch (err) {
|
|
2387
|
-
|
|
2833
|
+
authSpinner.stop();
|
|
2834
|
+
return `Error checking permissions: ${err.message}`;
|
|
2835
|
+
}
|
|
2836
|
+
if (!allowed) {
|
|
2837
|
+
return `Permission denied: only repository collaborators can update the project wiki.`;
|
|
2838
|
+
}
|
|
2839
|
+
const genSpinner = ora11("Analyzing project and generating overview\u2026").start();
|
|
2840
|
+
let content;
|
|
2841
|
+
try {
|
|
2842
|
+
content = await generateWiki(config);
|
|
2843
|
+
genSpinner.stop();
|
|
2844
|
+
} catch (err) {
|
|
2845
|
+
genSpinner.stop();
|
|
2846
|
+
return `Error generating wiki: ${err.message}`;
|
|
2847
|
+
}
|
|
2848
|
+
printWiki(content);
|
|
2849
|
+
let confirm;
|
|
2850
|
+
try {
|
|
2851
|
+
confirm = await select10({
|
|
2852
|
+
message: `Publish to repository as ${WIKI_PATH}?`,
|
|
2853
|
+
choices: [
|
|
2854
|
+
{ name: "Yes, commit to repo", value: "publish" },
|
|
2855
|
+
{ name: "Cancel", value: "cancel" }
|
|
2856
|
+
]
|
|
2857
|
+
});
|
|
2858
|
+
} catch {
|
|
2859
|
+
return "Cancelled.";
|
|
2860
|
+
}
|
|
2861
|
+
if (confirm === "cancel") return "Cancelled.";
|
|
2862
|
+
const writeSpinner = ora11(`Writing ${WIKI_PATH}\u2026`).start();
|
|
2863
|
+
try {
|
|
2864
|
+
const url = await upsertRepoFile(config, WIKI_PATH, content, "docs: update TECHUNTER.md project overview");
|
|
2865
|
+
writeSpinner.succeed(`Written: ${url}`);
|
|
2866
|
+
console.log("");
|
|
2867
|
+
return `TECHUNTER.md updated \u2014 ${url}`;
|
|
2868
|
+
} catch (err) {
|
|
2869
|
+
writeSpinner.fail(`Failed: ${err.message}`);
|
|
2388
2870
|
return `Error: ${err.message}`;
|
|
2389
2871
|
}
|
|
2390
2872
|
}
|
|
2391
|
-
async function
|
|
2392
|
-
const
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2873
|
+
async function execute13(_input, config) {
|
|
2874
|
+
const me = await getAuthenticatedUser(config);
|
|
2875
|
+
if (!await isCollaborator(config, me)) {
|
|
2876
|
+
return `Permission denied: only repository collaborators can update the project wiki.`;
|
|
2877
|
+
}
|
|
2878
|
+
const content = await generateWiki(config);
|
|
2396
2879
|
try {
|
|
2397
|
-
await
|
|
2398
|
-
|
|
2399
|
-
return `Task #${issueNumber} updated.`;
|
|
2880
|
+
const url = await upsertRepoFile(config, WIKI_PATH, content, "docs: update TECHUNTER.md project overview");
|
|
2881
|
+
return `TECHUNTER.md updated \u2014 ${url}`;
|
|
2400
2882
|
} catch (err) {
|
|
2401
|
-
spinner.stop();
|
|
2402
2883
|
return `Error: ${err.message}`;
|
|
2403
2884
|
}
|
|
2404
2885
|
}
|
|
2405
|
-
var
|
|
2886
|
+
var terminal13 = true;
|
|
2406
2887
|
|
|
2407
2888
|
// src/tools/list-tasks/index.ts
|
|
2408
2889
|
var list_tasks_exports = {};
|
|
2409
2890
|
__export(list_tasks_exports, {
|
|
2410
|
-
definition: () =>
|
|
2411
|
-
execute: () =>
|
|
2891
|
+
definition: () => definition14,
|
|
2892
|
+
execute: () => execute14
|
|
2412
2893
|
});
|
|
2413
2894
|
init_github();
|
|
2414
|
-
var
|
|
2895
|
+
var definition14 = {
|
|
2415
2896
|
type: "function",
|
|
2416
2897
|
function: {
|
|
2417
2898
|
name: "list_tasks",
|
|
@@ -2423,7 +2904,7 @@ var definition12 = {
|
|
|
2423
2904
|
}
|
|
2424
2905
|
}
|
|
2425
2906
|
};
|
|
2426
|
-
async function
|
|
2907
|
+
async function execute14(_input, config) {
|
|
2427
2908
|
const tasks = await listTasks(config);
|
|
2428
2909
|
if (tasks.length === 0) return "No open tasks.";
|
|
2429
2910
|
return tasks.map((t) => {
|
|
@@ -2436,11 +2917,11 @@ async function execute12(_input, config) {
|
|
|
2436
2917
|
// src/tools/get-task/index.ts
|
|
2437
2918
|
var get_task_exports = {};
|
|
2438
2919
|
__export(get_task_exports, {
|
|
2439
|
-
definition: () =>
|
|
2440
|
-
execute: () =>
|
|
2920
|
+
definition: () => definition15,
|
|
2921
|
+
execute: () => execute15
|
|
2441
2922
|
});
|
|
2442
2923
|
init_github();
|
|
2443
|
-
var
|
|
2924
|
+
var definition15 = {
|
|
2444
2925
|
type: "function",
|
|
2445
2926
|
function: {
|
|
2446
2927
|
name: "get_task",
|
|
@@ -2454,7 +2935,7 @@ var definition13 = {
|
|
|
2454
2935
|
}
|
|
2455
2936
|
}
|
|
2456
2937
|
};
|
|
2457
|
-
async function
|
|
2938
|
+
async function execute15(input3, config) {
|
|
2458
2939
|
const issue = await getTask(config, input3["issue_number"]);
|
|
2459
2940
|
const status = issue.labels.find((l) => l.startsWith("techunter:"))?.replace("techunter:", "") ?? "unknown";
|
|
2460
2941
|
const assignee = issue.assignee ? `@${issue.assignee}` : "\u2014";
|
|
@@ -2471,12 +2952,12 @@ ${issue.body}`);
|
|
|
2471
2952
|
// src/tools/get-comments/index.ts
|
|
2472
2953
|
var get_comments_exports = {};
|
|
2473
2954
|
__export(get_comments_exports, {
|
|
2474
|
-
definition: () =>
|
|
2475
|
-
execute: () =>
|
|
2955
|
+
definition: () => definition16,
|
|
2956
|
+
execute: () => execute16
|
|
2476
2957
|
});
|
|
2477
2958
|
init_github();
|
|
2478
|
-
import
|
|
2479
|
-
var
|
|
2959
|
+
import ora12 from "ora";
|
|
2960
|
+
var definition16 = {
|
|
2480
2961
|
type: "function",
|
|
2481
2962
|
function: {
|
|
2482
2963
|
name: "get_comments",
|
|
@@ -2491,10 +2972,10 @@ var definition14 = {
|
|
|
2491
2972
|
}
|
|
2492
2973
|
}
|
|
2493
2974
|
};
|
|
2494
|
-
async function
|
|
2975
|
+
async function execute16(input3, config) {
|
|
2495
2976
|
const issueNumber = input3["issue_number"];
|
|
2496
2977
|
const limit = input3["limit"] ?? 5;
|
|
2497
|
-
const spinner =
|
|
2978
|
+
const spinner = ora12(`Loading comments for #${issueNumber}...`).start();
|
|
2498
2979
|
try {
|
|
2499
2980
|
const comments = await listComments(config, issueNumber, limit);
|
|
2500
2981
|
spinner.stop();
|
|
@@ -2513,11 +2994,11 @@ ${lines.join("\n\n")}`;
|
|
|
2513
2994
|
// src/tools/get-diff/index.ts
|
|
2514
2995
|
var get_diff_exports = {};
|
|
2515
2996
|
__export(get_diff_exports, {
|
|
2516
|
-
definition: () =>
|
|
2517
|
-
execute: () =>
|
|
2997
|
+
definition: () => definition17,
|
|
2998
|
+
execute: () => execute17
|
|
2518
2999
|
});
|
|
2519
|
-
import
|
|
2520
|
-
var
|
|
3000
|
+
import ora13 from "ora";
|
|
3001
|
+
var definition17 = {
|
|
2521
3002
|
type: "function",
|
|
2522
3003
|
function: {
|
|
2523
3004
|
name: "get_diff",
|
|
@@ -2525,8 +3006,8 @@ var definition15 = {
|
|
|
2525
3006
|
parameters: { type: "object", properties: {}, required: [] }
|
|
2526
3007
|
}
|
|
2527
3008
|
};
|
|
2528
|
-
async function
|
|
2529
|
-
const spinner =
|
|
3009
|
+
async function execute17(_input, _config) {
|
|
3010
|
+
const spinner = ora13("Reading git diff...").start();
|
|
2530
3011
|
try {
|
|
2531
3012
|
const diff = await getDiff();
|
|
2532
3013
|
spinner.stop();
|
|
@@ -2540,14 +3021,14 @@ async function execute15(_input, _config) {
|
|
|
2540
3021
|
// src/tools/run-command/index.ts
|
|
2541
3022
|
var run_command_exports = {};
|
|
2542
3023
|
__export(run_command_exports, {
|
|
2543
|
-
definition: () =>
|
|
2544
|
-
execute: () =>
|
|
3024
|
+
definition: () => definition18,
|
|
3025
|
+
execute: () => execute18
|
|
2545
3026
|
});
|
|
2546
3027
|
import { exec } from "child_process";
|
|
2547
3028
|
import { promisify } from "util";
|
|
2548
|
-
import
|
|
3029
|
+
import ora14 from "ora";
|
|
2549
3030
|
var execAsync = promisify(exec);
|
|
2550
|
-
var
|
|
3031
|
+
var definition18 = {
|
|
2551
3032
|
type: "function",
|
|
2552
3033
|
function: {
|
|
2553
3034
|
name: "run_command",
|
|
@@ -2561,10 +3042,10 @@ var definition16 = {
|
|
|
2561
3042
|
}
|
|
2562
3043
|
}
|
|
2563
3044
|
};
|
|
2564
|
-
async function
|
|
3045
|
+
async function execute18(input3, _config) {
|
|
2565
3046
|
const command = input3["command"];
|
|
2566
3047
|
const cwd = process.cwd();
|
|
2567
|
-
const spinner =
|
|
3048
|
+
const spinner = ora14(`$ ${command}`).start();
|
|
2568
3049
|
try {
|
|
2569
3050
|
const { stdout, stderr } = await execAsync(command, { cwd, timeout: 6e4, maxBuffer: 1024 * 1024 });
|
|
2570
3051
|
spinner.stop();
|
|
@@ -2583,15 +3064,15 @@ ${out || e.message}`;
|
|
|
2583
3064
|
// src/tools/list-files/index.ts
|
|
2584
3065
|
var list_files_exports = {};
|
|
2585
3066
|
__export(list_files_exports, {
|
|
2586
|
-
definition: () =>
|
|
2587
|
-
execute: () =>
|
|
3067
|
+
definition: () => definition19,
|
|
3068
|
+
execute: () => execute19
|
|
2588
3069
|
});
|
|
2589
|
-
import { readFile as
|
|
3070
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
2590
3071
|
import { existsSync } from "fs";
|
|
2591
3072
|
import path2 from "path";
|
|
2592
3073
|
import { globby } from "globby";
|
|
2593
3074
|
import ignore from "ignore";
|
|
2594
|
-
var
|
|
3075
|
+
var definition19 = {
|
|
2595
3076
|
type: "function",
|
|
2596
3077
|
function: {
|
|
2597
3078
|
name: "list_files",
|
|
@@ -2631,13 +3112,13 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
2631
3112
|
".sqlite",
|
|
2632
3113
|
".lock"
|
|
2633
3114
|
]);
|
|
2634
|
-
async function
|
|
3115
|
+
async function execute19(input3, _config) {
|
|
2635
3116
|
const glob = input3["glob"] ?? "**/*";
|
|
2636
3117
|
const cwd = process.cwd();
|
|
2637
3118
|
const ig = ignore();
|
|
2638
3119
|
const gitignorePath = path2.join(cwd, ".gitignore");
|
|
2639
3120
|
if (existsSync(gitignorePath)) {
|
|
2640
|
-
ig.add(await
|
|
3121
|
+
ig.add(await readFile3(gitignorePath, "utf-8"));
|
|
2641
3122
|
}
|
|
2642
3123
|
ig.add(["node_modules", "dist", ".git", ".next", "__pycache__", "build", "coverage"]);
|
|
2643
3124
|
const files = await globby(glob, { cwd, dot: false, onlyFiles: true, gitignore: false });
|
|
@@ -2650,15 +3131,15 @@ ${filtered.join("\n")}`;
|
|
|
2650
3131
|
// src/tools/grep-code/index.ts
|
|
2651
3132
|
var grep_code_exports = {};
|
|
2652
3133
|
__export(grep_code_exports, {
|
|
2653
|
-
definition: () =>
|
|
2654
|
-
execute: () =>
|
|
3134
|
+
definition: () => definition20,
|
|
3135
|
+
execute: () => execute20
|
|
2655
3136
|
});
|
|
2656
|
-
import { readFile as
|
|
3137
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
2657
3138
|
import { existsSync as existsSync2 } from "fs";
|
|
2658
3139
|
import path3 from "path";
|
|
2659
3140
|
import { globby as globby2 } from "globby";
|
|
2660
3141
|
import ignore2 from "ignore";
|
|
2661
|
-
var
|
|
3142
|
+
var definition20 = {
|
|
2662
3143
|
type: "function",
|
|
2663
3144
|
function: {
|
|
2664
3145
|
name: "grep_code",
|
|
@@ -2699,7 +3180,7 @@ async function buildIgnore(cwd) {
|
|
|
2699
3180
|
const ig = ignore2();
|
|
2700
3181
|
const gitignorePath = path3.join(cwd, ".gitignore");
|
|
2701
3182
|
if (existsSync2(gitignorePath)) {
|
|
2702
|
-
ig.add(await
|
|
3183
|
+
ig.add(await readFile4(gitignorePath, "utf-8"));
|
|
2703
3184
|
}
|
|
2704
3185
|
ig.add(["node_modules", "dist", ".git", ".next", "__pycache__", "build", "coverage"]);
|
|
2705
3186
|
return ig;
|
|
@@ -2731,7 +3212,7 @@ function isText(f) {
|
|
|
2731
3212
|
return !BINARY_EXTENSIONS2.has(path3.extname(f).toLowerCase());
|
|
2732
3213
|
}
|
|
2733
3214
|
var MAX_RANGE_LINES = 300;
|
|
2734
|
-
async function
|
|
3215
|
+
async function execute20(input3, _config) {
|
|
2735
3216
|
const pattern = input3["pattern"] ?? "";
|
|
2736
3217
|
const fileGlob = input3["file_glob"] ?? "**/*";
|
|
2737
3218
|
const contextLines = Math.min(input3["context_lines"] ?? 2, 5);
|
|
@@ -2743,7 +3224,7 @@ async function execute18(input3, _config) {
|
|
|
2743
3224
|
const files = await globby2(fileGlob, { cwd, dot: false, onlyFiles: true, gitignore: false });
|
|
2744
3225
|
if (files.length === 0) return `No file matched: ${fileGlob}`;
|
|
2745
3226
|
if (files.length > 1) return `file_glob matched ${files.length} files \u2014 narrow it to a single file for read-range mode.`;
|
|
2746
|
-
const raw = await
|
|
3227
|
+
const raw = await readFile4(path3.join(cwd, files[0]), "utf-8");
|
|
2747
3228
|
const lines = raw.split("\n");
|
|
2748
3229
|
const total = lines.length;
|
|
2749
3230
|
const from = Math.max(1, startLine);
|
|
@@ -2756,146 +3237,512 @@ async function execute18(input3, _config) {
|
|
|
2756
3237
|
${numbered}
|
|
2757
3238
|
\`\`\`${truncNote}`;
|
|
2758
3239
|
}
|
|
2759
|
-
if (!pattern) return "Provide a `pattern` to search, or `start_line` + `end_line` for read-range mode. Use list_files to browse file paths.";
|
|
2760
|
-
const ig = await buildIgnore(cwd);
|
|
2761
|
-
let regex;
|
|
3240
|
+
if (!pattern) return "Provide a `pattern` to search, or `start_line` + `end_line` for read-range mode. Use list_files to browse file paths.";
|
|
3241
|
+
const ig = await buildIgnore(cwd);
|
|
3242
|
+
let regex;
|
|
3243
|
+
try {
|
|
3244
|
+
regex = new RegExp(pattern, "i");
|
|
3245
|
+
} catch {
|
|
3246
|
+
regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
3247
|
+
}
|
|
3248
|
+
const allFiles = await globby2(fileGlob, { cwd, dot: false, onlyFiles: true, gitignore: false });
|
|
3249
|
+
const filtered = allFiles.filter((f) => !ig.ignores(f) && isText(f));
|
|
3250
|
+
const matches = [];
|
|
3251
|
+
let totalMatches = 0;
|
|
3252
|
+
for (const file of filtered) {
|
|
3253
|
+
if (totalMatches >= maxResults) break;
|
|
3254
|
+
let content;
|
|
3255
|
+
try {
|
|
3256
|
+
content = await readFile4(path3.join(cwd, file), "utf-8");
|
|
3257
|
+
} catch {
|
|
3258
|
+
continue;
|
|
3259
|
+
}
|
|
3260
|
+
const lines = content.split("\n");
|
|
3261
|
+
const hitLines = [];
|
|
3262
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3263
|
+
if (regex.test(lines[i])) hitLines.push(i);
|
|
3264
|
+
}
|
|
3265
|
+
if (hitLines.length === 0) continue;
|
|
3266
|
+
const ranges = [];
|
|
3267
|
+
for (const hit of hitLines) {
|
|
3268
|
+
const s = Math.max(0, hit - contextLines);
|
|
3269
|
+
const e = Math.min(lines.length - 1, hit + contextLines);
|
|
3270
|
+
if (ranges.length > 0 && s <= ranges[ranges.length - 1][1] + 1) {
|
|
3271
|
+
ranges[ranges.length - 1][1] = e;
|
|
3272
|
+
} else {
|
|
3273
|
+
ranges.push([s, e]);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
const snippets = [];
|
|
3277
|
+
for (const [s, e] of ranges) {
|
|
3278
|
+
if (totalMatches >= maxResults) break;
|
|
3279
|
+
snippets.push(
|
|
3280
|
+
lines.slice(s, e + 1).map((l, i) => {
|
|
3281
|
+
const n = s + i + 1;
|
|
3282
|
+
return `${regex.test(l) ? ">" : " "} ${String(n).padStart(4)}: ${l}`;
|
|
3283
|
+
}).join("\n")
|
|
3284
|
+
);
|
|
3285
|
+
totalMatches += hitLines.filter((h) => h >= s && h <= e).length;
|
|
3286
|
+
}
|
|
3287
|
+
if (snippets.length > 0) {
|
|
3288
|
+
matches.push(`## ${file}
|
|
3289
|
+
\`\`\`
|
|
3290
|
+
${snippets.join("\n---\n")}
|
|
3291
|
+
\`\`\``);
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
if (matches.length === 0) return `No matches found for: ${pattern}`;
|
|
3295
|
+
const header = `Found matches in ${matches.length} file(s) (${totalMatches} match${totalMatches === 1 ? "" : "es"})${totalMatches >= maxResults ? " \u2014 limit reached" : ""}:`;
|
|
3296
|
+
return [header, ...matches].join("\n\n");
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
// src/tools/ask-user/index.ts
|
|
3300
|
+
var ask_user_exports = {};
|
|
3301
|
+
__export(ask_user_exports, {
|
|
3302
|
+
definition: () => definition21,
|
|
3303
|
+
execute: () => execute21
|
|
3304
|
+
});
|
|
3305
|
+
import chalk12 from "chalk";
|
|
3306
|
+
import { select as select11, input as promptInput5 } from "@inquirer/prompts";
|
|
3307
|
+
var definition21 = {
|
|
3308
|
+
type: "function",
|
|
3309
|
+
function: {
|
|
3310
|
+
name: "ask_user",
|
|
3311
|
+
description: "Ask the user to clarify something ambiguous \u2014 scope, expected behaviour, edge cases, or business decisions. Do NOT ask about technical implementation choices. Use at most 3 times per task.",
|
|
3312
|
+
parameters: {
|
|
3313
|
+
type: "object",
|
|
3314
|
+
properties: {
|
|
3315
|
+
question: { type: "string", description: "The question to ask the user" },
|
|
3316
|
+
options: {
|
|
3317
|
+
type: "array",
|
|
3318
|
+
items: { type: "string" },
|
|
3319
|
+
description: "2\u20134 concrete answer choices"
|
|
3320
|
+
}
|
|
3321
|
+
},
|
|
3322
|
+
required: ["question", "options"]
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
};
|
|
3326
|
+
async function execute21(input3, _config) {
|
|
3327
|
+
const question = input3["question"];
|
|
3328
|
+
const options = input3["options"];
|
|
3329
|
+
const OTHER = "__other__";
|
|
3330
|
+
console.log("");
|
|
3331
|
+
console.log(chalk12.dim(" \u250C\u2500 Agent question " + "\u2500".repeat(51)));
|
|
3332
|
+
console.log(chalk12.dim(" \u2502"));
|
|
3333
|
+
for (const line of question.split("\n")) {
|
|
3334
|
+
console.log(chalk12.dim(" \u2502 ") + line);
|
|
3335
|
+
}
|
|
3336
|
+
console.log(chalk12.dim(" \u2514" + "\u2500".repeat(67)));
|
|
3337
|
+
let answer;
|
|
3338
|
+
try {
|
|
3339
|
+
const chosen = await select11({
|
|
3340
|
+
message: " ",
|
|
3341
|
+
choices: [
|
|
3342
|
+
...options.map((o) => ({ name: o, value: o })),
|
|
3343
|
+
{ name: chalk12.dim("Other (describe below)"), value: OTHER }
|
|
3344
|
+
]
|
|
3345
|
+
});
|
|
3346
|
+
answer = chosen === OTHER ? await promptInput5({ message: "Your answer:" }) : chosen;
|
|
3347
|
+
} catch {
|
|
3348
|
+
answer = "User skipped this question \u2014 use your best judgement.";
|
|
3349
|
+
}
|
|
3350
|
+
console.log("");
|
|
3351
|
+
return answer;
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
// src/tools/registry.ts
|
|
3355
|
+
var toolModules = [
|
|
3356
|
+
// Command tools
|
|
3357
|
+
pick_exports,
|
|
3358
|
+
new_task_exports,
|
|
3359
|
+
close_exports,
|
|
3360
|
+
submit_exports,
|
|
3361
|
+
my_status_exports,
|
|
3362
|
+
review_exports,
|
|
3363
|
+
refresh_exports,
|
|
3364
|
+
open_code_exports,
|
|
3365
|
+
reject_exports,
|
|
3366
|
+
accept_exports,
|
|
3367
|
+
edit_task_exports,
|
|
3368
|
+
move_task_exports,
|
|
3369
|
+
wiki_exports,
|
|
3370
|
+
// Low-level tools
|
|
3371
|
+
list_tasks_exports,
|
|
3372
|
+
get_task_exports,
|
|
3373
|
+
get_comments_exports,
|
|
3374
|
+
get_diff_exports,
|
|
3375
|
+
run_command_exports,
|
|
3376
|
+
list_files_exports,
|
|
3377
|
+
grep_code_exports,
|
|
3378
|
+
ask_user_exports
|
|
3379
|
+
];
|
|
3380
|
+
|
|
3381
|
+
// src/lib/agent-ui.ts
|
|
3382
|
+
import chalk13 from "chalk";
|
|
3383
|
+
function formatInput(input3) {
|
|
3384
|
+
return Object.entries(input3).map(([k, v]) => {
|
|
3385
|
+
if (typeof v === "number") return `${k}=${v}`;
|
|
3386
|
+
if (typeof v === "string") {
|
|
3387
|
+
if (k === "body" || v.length > 50) return `${k}=[${v.length} chars]`;
|
|
3388
|
+
return `${k}="${v}"`;
|
|
3389
|
+
}
|
|
3390
|
+
return `${k}=${JSON.stringify(v)}`;
|
|
3391
|
+
}).join(" ");
|
|
3392
|
+
}
|
|
3393
|
+
function summarize(result) {
|
|
3394
|
+
const first = result.split("\n").find((l) => l.trim()) ?? result;
|
|
3395
|
+
return first.length > 100 ? first.slice(0, 97) + "..." : first;
|
|
3396
|
+
}
|
|
3397
|
+
function printToolCall(name, input3) {
|
|
3398
|
+
const params = formatInput(input3);
|
|
3399
|
+
console.log(` ${chalk13.cyan("\u2192")} ${chalk13.bold(name)}${params ? " " + chalk13.dim(params) : ""}`);
|
|
3400
|
+
}
|
|
3401
|
+
function printToolResult(result) {
|
|
3402
|
+
const ok = !result.startsWith("Error:");
|
|
3403
|
+
const icon = ok ? chalk13.green("\u2713") : chalk13.red("\u2717");
|
|
3404
|
+
console.log(` ${icon} ${chalk13.dim(summarize(result))}`);
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
// src/lib/sub-agent.ts
|
|
3408
|
+
async function runSubAgentLoop(config, systemPrompt, userMessage, toolNames) {
|
|
3409
|
+
const client = createClient(config);
|
|
3410
|
+
const selected = toolModules.filter((m) => toolNames.includes(m.definition.function.name));
|
|
3411
|
+
const tools2 = selected.map((m) => m.definition);
|
|
3412
|
+
const messages = [
|
|
3413
|
+
{ role: "system", content: systemPrompt },
|
|
3414
|
+
{ role: "user", content: userMessage }
|
|
3415
|
+
];
|
|
3416
|
+
const MAX_ITERATIONS = 100;
|
|
3417
|
+
let iterations = 0;
|
|
3418
|
+
for (; ; ) {
|
|
3419
|
+
if (++iterations > MAX_ITERATIONS) {
|
|
3420
|
+
throw new Error(`Sub-agent exceeded ${MAX_ITERATIONS} iterations without finishing.`);
|
|
3421
|
+
}
|
|
3422
|
+
const res = await client.chat.completions.create({ model: getModel(config), tools: tools2, messages });
|
|
3423
|
+
const choice = res.choices[0];
|
|
3424
|
+
messages.push({
|
|
3425
|
+
role: "assistant",
|
|
3426
|
+
content: choice.message.content ?? null,
|
|
3427
|
+
...choice.message.tool_calls ? { tool_calls: choice.message.tool_calls } : {}
|
|
3428
|
+
});
|
|
3429
|
+
if (choice.finish_reason === "stop") {
|
|
3430
|
+
return choice.message.content ?? "";
|
|
3431
|
+
}
|
|
3432
|
+
if (choice.finish_reason === "tool_calls") {
|
|
3433
|
+
for (const tc of choice.message.tool_calls ?? []) {
|
|
3434
|
+
let input3;
|
|
3435
|
+
try {
|
|
3436
|
+
input3 = JSON.parse(tc.function.arguments);
|
|
3437
|
+
} catch {
|
|
3438
|
+
input3 = {};
|
|
3439
|
+
}
|
|
3440
|
+
printToolCall(tc.function.name, input3);
|
|
3441
|
+
const mod = selected.find((m) => m.definition.function.name === tc.function.name);
|
|
3442
|
+
const result = mod ? await mod.execute(input3, config) : `Unknown tool: ${tc.function.name}`;
|
|
3443
|
+
printToolResult(result);
|
|
3444
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: result });
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
// src/tools/wiki/prompts.ts
|
|
3451
|
+
var WIKI_FORMAT = `
|
|
3452
|
+
The document you produce must be valid Markdown with these exact sections:
|
|
3453
|
+
|
|
3454
|
+
# [Project Name]
|
|
3455
|
+
|
|
3456
|
+
> One-sentence description of what this project does.
|
|
3457
|
+
|
|
3458
|
+
## What Is This?
|
|
3459
|
+
|
|
3460
|
+
2-4 paragraphs covering:
|
|
3461
|
+
- The problem this project solves
|
|
3462
|
+
- Who uses it and in what context
|
|
3463
|
+
- Core capabilities / key features
|
|
3464
|
+
|
|
3465
|
+
## Quick Start
|
|
3466
|
+
|
|
3467
|
+
Numbered steps for a brand-new developer to install, configure, and run the project for the first time.
|
|
3468
|
+
|
|
3469
|
+
## Architecture
|
|
3470
|
+
|
|
3471
|
+
High-level explanation of how the system is structured:
|
|
3472
|
+
- Key components / layers and their responsibilities
|
|
3473
|
+
- Request or data flow (prose or ASCII diagram)
|
|
3474
|
+
- Noteworthy design decisions
|
|
3475
|
+
|
|
3476
|
+
## Key Files
|
|
3477
|
+
|
|
3478
|
+
| File / Directory | Purpose |
|
|
3479
|
+
|---|---|
|
|
3480
|
+
| ... | ... |
|
|
3481
|
+
|
|
3482
|
+
(List the 8-15 most important files.)
|
|
3483
|
+
|
|
3484
|
+
## Development Workflow
|
|
3485
|
+
|
|
3486
|
+
Common day-to-day tasks a contributor will need:
|
|
3487
|
+
- How to build / run locally
|
|
3488
|
+
- How to add a new feature (brief steps)
|
|
3489
|
+
- Any testing or linting commands
|
|
3490
|
+
|
|
3491
|
+
---
|
|
3492
|
+
*Maintained by Techunter \u2014 run \`tch wiki\` to regenerate*
|
|
3493
|
+
`;
|
|
3494
|
+
|
|
3495
|
+
// src/tools/wiki/wiki-generator.ts
|
|
3496
|
+
async function generateWiki(config) {
|
|
3497
|
+
return runSubAgentLoop(
|
|
3498
|
+
config,
|
|
3499
|
+
"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,
|
|
3500
|
+
"Analyze this project thoroughly and produce a comprehensive TECHUNTER.md overview document for new team members.",
|
|
3501
|
+
["list_files", "grep_code", "run_command"]
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
// src/commands/init.ts
|
|
3506
|
+
async function getGitHubTokenViaPAT() {
|
|
3507
|
+
console.log(chalk14.dim("\n Create a token at: https://github.com/settings/tokens/new"));
|
|
3508
|
+
console.log(chalk14.dim(" Required scopes: repo, read:user\n"));
|
|
3509
|
+
const token = await password({
|
|
3510
|
+
message: "GitHub Personal Access Token:",
|
|
3511
|
+
mask: "*"
|
|
3512
|
+
});
|
|
3513
|
+
return { token: token.trim() };
|
|
3514
|
+
}
|
|
3515
|
+
var OAUTH_CLIENT_ID = "Ov23liW4zJ4r2RdZOsCJ";
|
|
3516
|
+
async function getGitHubTokenViaDeviceFlow() {
|
|
3517
|
+
let verificationUri = "";
|
|
3518
|
+
let userCode = "";
|
|
3519
|
+
const auth = createOAuthDeviceAuth({
|
|
3520
|
+
clientType: "oauth-app",
|
|
3521
|
+
clientId: OAUTH_CLIENT_ID,
|
|
3522
|
+
scopes: ["repo"],
|
|
3523
|
+
onVerification(verification) {
|
|
3524
|
+
verificationUri = verification.verification_uri;
|
|
3525
|
+
userCode = verification.user_code;
|
|
3526
|
+
console.log("");
|
|
3527
|
+
console.log(chalk14.bold(" 1. Open this URL in your browser:"));
|
|
3528
|
+
console.log(" " + chalk14.cyan(verificationUri));
|
|
3529
|
+
console.log("");
|
|
3530
|
+
console.log(chalk14.bold(" 2. Enter this code:"));
|
|
3531
|
+
console.log(" " + chalk14.yellow.bold(userCode));
|
|
3532
|
+
console.log("");
|
|
3533
|
+
open2(verificationUri).catch(() => {
|
|
3534
|
+
});
|
|
3535
|
+
}
|
|
3536
|
+
});
|
|
3537
|
+
const spinner = ora15("Waiting for authorization in browser...").start();
|
|
3538
|
+
let token;
|
|
3539
|
+
try {
|
|
3540
|
+
const result = await auth({ type: "oauth" });
|
|
3541
|
+
token = result.token;
|
|
3542
|
+
spinner.succeed("Authorized!");
|
|
3543
|
+
} catch (err) {
|
|
3544
|
+
spinner.fail("Authorization failed");
|
|
3545
|
+
throw err;
|
|
3546
|
+
}
|
|
3547
|
+
return { token, clientId: OAUTH_CLIENT_ID };
|
|
3548
|
+
}
|
|
3549
|
+
async function initCommand() {
|
|
3550
|
+
console.log(chalk14.bold.cyan("\nTechunter \u2014 Initial Setup\n"));
|
|
3551
|
+
let detectedOwner = "";
|
|
3552
|
+
let detectedRepo = "";
|
|
3553
|
+
const remoteUrl = await getRemoteUrl();
|
|
3554
|
+
if (remoteUrl) {
|
|
3555
|
+
const parsed = parseOwnerRepo(remoteUrl);
|
|
3556
|
+
if (parsed) {
|
|
3557
|
+
detectedOwner = parsed.owner;
|
|
3558
|
+
detectedRepo = parsed.repo;
|
|
3559
|
+
console.log(chalk14.dim(`Detected GitHub repo: ${detectedOwner}/${detectedRepo}
|
|
3560
|
+
`));
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
const authMethod = await select12({
|
|
3564
|
+
message: "How would you like to authenticate with GitHub?",
|
|
3565
|
+
choices: [
|
|
3566
|
+
{
|
|
3567
|
+
name: "Browser login (OAuth) \u2014 open a URL and click Authorize",
|
|
3568
|
+
value: "device"
|
|
3569
|
+
},
|
|
3570
|
+
{
|
|
3571
|
+
name: "Personal Access Token (PAT) \u2014 paste a token from github.com/settings/tokens",
|
|
3572
|
+
value: "pat"
|
|
3573
|
+
}
|
|
3574
|
+
]
|
|
3575
|
+
});
|
|
3576
|
+
let githubToken;
|
|
3577
|
+
let githubClientId;
|
|
3578
|
+
if (authMethod === "device") {
|
|
3579
|
+
const result = await getGitHubTokenViaDeviceFlow();
|
|
3580
|
+
githubToken = result.token;
|
|
3581
|
+
githubClientId = result.clientId;
|
|
3582
|
+
} else {
|
|
3583
|
+
const result = await getGitHubTokenViaPAT();
|
|
3584
|
+
githubToken = result.token;
|
|
3585
|
+
}
|
|
3586
|
+
const providerChoice = await select12({
|
|
3587
|
+
message: "AI provider:",
|
|
3588
|
+
choices: [
|
|
3589
|
+
{ name: `OpenRouter (recommended) ${chalk14.dim(`${DEFAULT_BASE_URL} \xB7 ${DEFAULT_MODEL}`)}`, value: "openrouter" },
|
|
3590
|
+
{ name: "Custom (specify base URL and model)", value: "custom" }
|
|
3591
|
+
]
|
|
3592
|
+
});
|
|
3593
|
+
let aiBaseUrl;
|
|
3594
|
+
let aiModel;
|
|
3595
|
+
if (providerChoice === "custom") {
|
|
3596
|
+
aiBaseUrl = (await input({ message: "API base URL:", default: DEFAULT_BASE_URL })).trim();
|
|
3597
|
+
aiModel = (await input({ message: "Model name:", default: DEFAULT_MODEL })).trim();
|
|
3598
|
+
}
|
|
3599
|
+
const apiKeyHint = providerChoice === "openrouter" ? chalk14.dim(" Get a key at: https://openrouter.ai/settings/keys\n") : chalk14.dim(" API key for your provider\n");
|
|
3600
|
+
console.log(apiKeyHint);
|
|
3601
|
+
const aiApiKey = await password({
|
|
3602
|
+
message: "API Key:",
|
|
3603
|
+
mask: "*"
|
|
3604
|
+
});
|
|
3605
|
+
let owner = detectedOwner;
|
|
3606
|
+
let repo = detectedRepo;
|
|
3607
|
+
if (!owner || !repo) {
|
|
3608
|
+
owner = await input({
|
|
3609
|
+
message: "GitHub repo owner (user or org):",
|
|
3610
|
+
required: true
|
|
3611
|
+
});
|
|
3612
|
+
repo = await input({
|
|
3613
|
+
message: "GitHub repo name:",
|
|
3614
|
+
required: true
|
|
3615
|
+
});
|
|
3616
|
+
}
|
|
3617
|
+
const config = {
|
|
3618
|
+
githubToken,
|
|
3619
|
+
githubClientId,
|
|
3620
|
+
aiApiKey: aiApiKey.trim(),
|
|
3621
|
+
...aiBaseUrl ? { aiBaseUrl } : {},
|
|
3622
|
+
...aiModel ? { aiModel } : {},
|
|
3623
|
+
github: {
|
|
3624
|
+
owner: owner.trim(),
|
|
3625
|
+
repo: repo.trim()
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
setConfig(config);
|
|
3629
|
+
const spinner = ora15("Setting up GitHub labels...").start();
|
|
2762
3630
|
try {
|
|
2763
|
-
|
|
3631
|
+
await ensureLabels(config);
|
|
3632
|
+
spinner.succeed("GitHub labels created");
|
|
3633
|
+
} catch (err) {
|
|
3634
|
+
spinner.fail("Failed to create labels (check token permissions)");
|
|
3635
|
+
console.error(chalk14.red(String(err)));
|
|
3636
|
+
}
|
|
3637
|
+
console.log(chalk14.green("\nSetup complete!"));
|
|
3638
|
+
console.log(chalk14.dim(`Config saved to: ${getConfigPath()}
|
|
3639
|
+
`));
|
|
3640
|
+
let genWiki = false;
|
|
3641
|
+
try {
|
|
3642
|
+
genWiki = await select12({
|
|
3643
|
+
message: "Generate TECHUNTER.md project overview for new team members?",
|
|
3644
|
+
choices: [
|
|
3645
|
+
{ name: "Yes, generate now", value: true },
|
|
3646
|
+
{ name: "No, skip (run /wiki later)", value: false }
|
|
3647
|
+
]
|
|
3648
|
+
});
|
|
2764
3649
|
} catch {
|
|
2765
|
-
regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
2766
3650
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
const matches = [];
|
|
2770
|
-
let totalMatches = 0;
|
|
2771
|
-
for (const file of filtered) {
|
|
2772
|
-
if (totalMatches >= maxResults) break;
|
|
2773
|
-
let content;
|
|
3651
|
+
if (genWiki) {
|
|
3652
|
+
const wikiSpinner = ora15("Analyzing project and generating TECHUNTER.md\u2026").start();
|
|
2774
3653
|
try {
|
|
2775
|
-
content = await
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2780
|
-
const hitLines = [];
|
|
2781
|
-
for (let i = 0; i < lines.length; i++) {
|
|
2782
|
-
if (regex.test(lines[i])) hitLines.push(i);
|
|
2783
|
-
}
|
|
2784
|
-
if (hitLines.length === 0) continue;
|
|
2785
|
-
const ranges = [];
|
|
2786
|
-
for (const hit of hitLines) {
|
|
2787
|
-
const s = Math.max(0, hit - contextLines);
|
|
2788
|
-
const e = Math.min(lines.length - 1, hit + contextLines);
|
|
2789
|
-
if (ranges.length > 0 && s <= ranges[ranges.length - 1][1] + 1) {
|
|
2790
|
-
ranges[ranges.length - 1][1] = e;
|
|
2791
|
-
} else {
|
|
2792
|
-
ranges.push([s, e]);
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
|
-
const snippets = [];
|
|
2796
|
-
for (const [s, e] of ranges) {
|
|
2797
|
-
if (totalMatches >= maxResults) break;
|
|
2798
|
-
snippets.push(
|
|
2799
|
-
lines.slice(s, e + 1).map((l, i) => {
|
|
2800
|
-
const n = s + i + 1;
|
|
2801
|
-
return `${regex.test(l) ? ">" : " "} ${String(n).padStart(4)}: ${l}`;
|
|
2802
|
-
}).join("\n")
|
|
2803
|
-
);
|
|
2804
|
-
totalMatches += hitLines.filter((h) => h >= s && h <= e).length;
|
|
2805
|
-
}
|
|
2806
|
-
if (snippets.length > 0) {
|
|
2807
|
-
matches.push(`## ${file}
|
|
2808
|
-
\`\`\`
|
|
2809
|
-
${snippets.join("\n---\n")}
|
|
2810
|
-
\`\`\``);
|
|
3654
|
+
const content = await generateWiki(config);
|
|
3655
|
+
await upsertRepoFile(config, "TECHUNTER.md", content, "docs: add TECHUNTER.md project overview");
|
|
3656
|
+
wikiSpinner.succeed("TECHUNTER.md created");
|
|
3657
|
+
} catch (err) {
|
|
3658
|
+
wikiSpinner.fail(`Could not generate wiki: ${err.message}`);
|
|
2811
3659
|
}
|
|
3660
|
+
console.log("");
|
|
2812
3661
|
}
|
|
2813
|
-
if (matches.length === 0) return `No matches found for: ${pattern}`;
|
|
2814
|
-
const header = `Found matches in ${matches.length} file(s) (${totalMatches} match${totalMatches === 1 ? "" : "es"})${totalMatches >= maxResults ? " \u2014 limit reached" : ""}:`;
|
|
2815
|
-
return [header, ...matches].join("\n\n");
|
|
2816
3662
|
}
|
|
2817
3663
|
|
|
2818
|
-
// src/
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
});
|
|
2824
|
-
import chalk12 from "chalk";
|
|
2825
|
-
import { select as select10, input as promptInput5 } from "@inquirer/prompts";
|
|
2826
|
-
var definition19 = {
|
|
2827
|
-
type: "function",
|
|
2828
|
-
function: {
|
|
2829
|
-
name: "ask_user",
|
|
2830
|
-
description: "Ask the user to clarify something ambiguous \u2014 scope, expected behaviour, edge cases, or business decisions. Do NOT ask about technical implementation choices. Use at most 3 times per task.",
|
|
2831
|
-
parameters: {
|
|
2832
|
-
type: "object",
|
|
2833
|
-
properties: {
|
|
2834
|
-
question: { type: "string", description: "The question to ask the user" },
|
|
2835
|
-
options: {
|
|
2836
|
-
type: "array",
|
|
2837
|
-
items: { type: "string" },
|
|
2838
|
-
description: "2\u20134 concrete answer choices"
|
|
2839
|
-
}
|
|
2840
|
-
},
|
|
2841
|
-
required: ["question", "options"]
|
|
2842
|
-
}
|
|
2843
|
-
}
|
|
2844
|
-
};
|
|
2845
|
-
async function execute19(input3, _config) {
|
|
2846
|
-
const question = input3["question"];
|
|
2847
|
-
const options = input3["options"];
|
|
2848
|
-
const OTHER = "__other__";
|
|
2849
|
-
console.log("");
|
|
2850
|
-
console.log(chalk12.dim(" \u250C\u2500 Agent question " + "\u2500".repeat(51)));
|
|
2851
|
-
console.log(chalk12.dim(" \u2502"));
|
|
2852
|
-
for (const line of question.split("\n")) {
|
|
2853
|
-
console.log(chalk12.dim(" \u2502 ") + line);
|
|
2854
|
-
}
|
|
2855
|
-
console.log(chalk12.dim(" \u2514" + "\u2500".repeat(67)));
|
|
2856
|
-
let answer;
|
|
3664
|
+
// src/commands/config.ts
|
|
3665
|
+
import { input as input2, password as password2, select as select13 } from "@inquirer/prompts";
|
|
3666
|
+
import chalk15 from "chalk";
|
|
3667
|
+
async function configCommand() {
|
|
3668
|
+
let config;
|
|
2857
3669
|
try {
|
|
2858
|
-
|
|
2859
|
-
message: " ",
|
|
2860
|
-
choices: [
|
|
2861
|
-
...options.map((o) => ({ name: o, value: o })),
|
|
2862
|
-
{ name: chalk12.dim("Other (describe below)"), value: OTHER }
|
|
2863
|
-
]
|
|
2864
|
-
});
|
|
2865
|
-
answer = chosen === OTHER ? await promptInput5({ message: "Your answer:" }) : chosen;
|
|
3670
|
+
config = getConfig();
|
|
2866
3671
|
} catch {
|
|
2867
|
-
|
|
3672
|
+
console.error(chalk15.red("No config found. Run `tch init` first."));
|
|
3673
|
+
process.exit(1);
|
|
3674
|
+
}
|
|
3675
|
+
console.log(chalk15.bold.cyan("\nTechunter \u2014 Settings\n"));
|
|
3676
|
+
console.log(chalk15.dim(`Config file: ${getConfigPath()}
|
|
3677
|
+
`));
|
|
3678
|
+
const currentBaseUrl = config.aiBaseUrl ?? DEFAULT_BASE_URL;
|
|
3679
|
+
const currentModel = config.aiModel ?? DEFAULT_MODEL;
|
|
3680
|
+
const currentBaseBranch = config.baseBranch ?? "main";
|
|
3681
|
+
const field = await select13({
|
|
3682
|
+
message: "Which setting to change?",
|
|
3683
|
+
choices: [
|
|
3684
|
+
{ name: `GitHub repo ${chalk15.dim(`${config.github.owner}/${config.github.repo}`)}`, value: "repo" },
|
|
3685
|
+
{ name: `Base branch ${chalk15.dim(currentBaseBranch)}`, value: "baseBranch" },
|
|
3686
|
+
{ name: `AI base URL ${chalk15.dim(currentBaseUrl)}`, value: "aiBaseUrl" },
|
|
3687
|
+
{ name: `AI model ${chalk15.dim(currentModel)}`, value: "aiModel" },
|
|
3688
|
+
{ name: `AI API Key ${chalk15.dim("(hidden)")}`, value: "aiApiKey" },
|
|
3689
|
+
{ name: `GitHub Token ${chalk15.dim("(hidden)")}`, value: "githubToken" },
|
|
3690
|
+
{ name: "Cancel", value: "cancel" }
|
|
3691
|
+
]
|
|
3692
|
+
});
|
|
3693
|
+
if (field === "cancel") return;
|
|
3694
|
+
if (field === "baseBranch") {
|
|
3695
|
+
const val = await input2({ message: "Base branch name:", default: currentBaseBranch });
|
|
3696
|
+
if (val.trim()) {
|
|
3697
|
+
setConfig({ baseBranch: val.trim() });
|
|
3698
|
+
console.log(chalk15.green(`
|
|
3699
|
+
Base branch set to: ${val.trim()}
|
|
3700
|
+
`));
|
|
3701
|
+
}
|
|
3702
|
+
} else if (field === "repo") {
|
|
3703
|
+
const owner = await input2({ message: "GitHub repo owner:", default: config.github.owner });
|
|
3704
|
+
const repo = await input2({ message: "GitHub repo name:", default: config.github.repo });
|
|
3705
|
+
setConfig({ github: { ...config.github, owner: owner.trim(), repo: repo.trim() } });
|
|
3706
|
+
console.log(chalk15.green(`
|
|
3707
|
+
Repo set to: ${owner.trim()}/${repo.trim()}
|
|
3708
|
+
`));
|
|
3709
|
+
} else if (field === "aiBaseUrl") {
|
|
3710
|
+
const val = await input2({ message: "AI base URL:", default: currentBaseUrl });
|
|
3711
|
+
if (val.trim()) {
|
|
3712
|
+
setConfig({ aiBaseUrl: val.trim() });
|
|
3713
|
+
console.log(chalk15.green(`
|
|
3714
|
+
AI base URL set to: ${val.trim()}
|
|
3715
|
+
`));
|
|
3716
|
+
}
|
|
3717
|
+
} else if (field === "aiModel") {
|
|
3718
|
+
const val = await input2({ message: "AI model name:", default: currentModel });
|
|
3719
|
+
if (val.trim()) {
|
|
3720
|
+
setConfig({ aiModel: val.trim() });
|
|
3721
|
+
console.log(chalk15.green(`
|
|
3722
|
+
AI model set to: ${val.trim()}
|
|
3723
|
+
`));
|
|
3724
|
+
}
|
|
3725
|
+
} else if (field === "aiApiKey") {
|
|
3726
|
+
const val = await password2({ message: "New AI API Key:", mask: "*" });
|
|
3727
|
+
if (val.trim()) {
|
|
3728
|
+
setConfig({ aiApiKey: val.trim() });
|
|
3729
|
+
console.log(chalk15.green("\nAI API Key updated.\n"));
|
|
3730
|
+
}
|
|
3731
|
+
} else if (field === "githubToken") {
|
|
3732
|
+
const val = await password2({ message: "New GitHub Token:", mask: "*" });
|
|
3733
|
+
if (val.trim()) {
|
|
3734
|
+
setConfig({ githubToken: val.trim() });
|
|
3735
|
+
console.log(chalk15.green("\nGitHub Token updated.\n"));
|
|
3736
|
+
}
|
|
2868
3737
|
}
|
|
2869
|
-
console.log("");
|
|
2870
|
-
return answer;
|
|
2871
3738
|
}
|
|
2872
3739
|
|
|
2873
|
-
// src/
|
|
2874
|
-
|
|
2875
|
-
// Command tools
|
|
2876
|
-
pick_exports,
|
|
2877
|
-
new_task_exports,
|
|
2878
|
-
close_exports,
|
|
2879
|
-
submit_exports,
|
|
2880
|
-
my_status_exports,
|
|
2881
|
-
review_exports,
|
|
2882
|
-
refresh_exports,
|
|
2883
|
-
open_code_exports,
|
|
2884
|
-
reject_exports,
|
|
2885
|
-
accept_exports,
|
|
2886
|
-
edit_task_exports,
|
|
2887
|
-
// Low-level tools
|
|
2888
|
-
list_tasks_exports,
|
|
2889
|
-
get_task_exports,
|
|
2890
|
-
get_comments_exports,
|
|
2891
|
-
get_diff_exports,
|
|
2892
|
-
run_command_exports,
|
|
2893
|
-
list_files_exports,
|
|
2894
|
-
grep_code_exports,
|
|
2895
|
-
ask_user_exports
|
|
2896
|
-
];
|
|
3740
|
+
// src/index.ts
|
|
3741
|
+
init_github();
|
|
2897
3742
|
|
|
2898
3743
|
// src/lib/agent.ts
|
|
3744
|
+
import ora16 from "ora";
|
|
3745
|
+
import chalk16 from "chalk";
|
|
2899
3746
|
var tools = toolModules.map((m) => m.definition);
|
|
2900
3747
|
var HISTORY_KEEP_TURNS = 8;
|
|
2901
3748
|
function trimHistory(messages) {
|
|
@@ -2981,7 +3828,7 @@ async function runAgentLoop(config, messages) {
|
|
|
2981
3828
|
throw new Error(`Agent exceeded ${MAX_ITERATIONS} iterations without finishing.`);
|
|
2982
3829
|
}
|
|
2983
3830
|
trimHistory(messages);
|
|
2984
|
-
const spinner =
|
|
3831
|
+
const spinner = ora16({ text: chalk16.dim("Thinking\u2026"), color: "cyan" }).start();
|
|
2985
3832
|
let response;
|
|
2986
3833
|
try {
|
|
2987
3834
|
response = await client.chat.completions.create({
|
|
@@ -3026,7 +3873,7 @@ async function runAgentLoop(config, messages) {
|
|
|
3026
3873
|
return executeTool(tc.function.name, parsed, config);
|
|
3027
3874
|
})
|
|
3028
3875
|
);
|
|
3029
|
-
let
|
|
3876
|
+
let terminal14 = false;
|
|
3030
3877
|
for (let i = 0; i < toolCalls.length; i++) {
|
|
3031
3878
|
printToolResult(results[i]);
|
|
3032
3879
|
messages.push({
|
|
@@ -3035,16 +3882,93 @@ async function runAgentLoop(config, messages) {
|
|
|
3035
3882
|
content: results[i]
|
|
3036
3883
|
});
|
|
3037
3884
|
if (toolModules.find((m) => m.definition.function.name === toolCalls[i].function.name)?.terminal) {
|
|
3038
|
-
|
|
3885
|
+
terminal14 = true;
|
|
3039
3886
|
}
|
|
3040
3887
|
}
|
|
3041
|
-
if (
|
|
3888
|
+
if (terminal14) return results[results.length - 1];
|
|
3042
3889
|
} else {
|
|
3043
3890
|
return choice.message.content ?? "";
|
|
3044
3891
|
}
|
|
3045
3892
|
}
|
|
3046
3893
|
}
|
|
3047
3894
|
|
|
3895
|
+
// src/lib/update-check.ts
|
|
3896
|
+
import Conf2 from "conf";
|
|
3897
|
+
import chalk17 from "chalk";
|
|
3898
|
+
import { execFile } from "child_process";
|
|
3899
|
+
var PACKAGE_NAME = "techunter";
|
|
3900
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
3901
|
+
var cache = new Conf2({
|
|
3902
|
+
projectName: "techunter-update-cache",
|
|
3903
|
+
defaults: { lastChecked: 0, latestVersion: "" }
|
|
3904
|
+
});
|
|
3905
|
+
async function fetchLatestVersion() {
|
|
3906
|
+
try {
|
|
3907
|
+
const { fetch } = await import("undici");
|
|
3908
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
3909
|
+
signal: AbortSignal.timeout(5e3)
|
|
3910
|
+
});
|
|
3911
|
+
if (!res.ok) return null;
|
|
3912
|
+
const data = await res.json();
|
|
3913
|
+
return data.version ?? null;
|
|
3914
|
+
} catch {
|
|
3915
|
+
return null;
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
function isNewer(latest, current) {
|
|
3919
|
+
const parse = (v) => v.split(".").map(Number);
|
|
3920
|
+
const [la, lb, lc] = parse(latest);
|
|
3921
|
+
const [ca, cb, cc] = parse(current);
|
|
3922
|
+
if (la !== ca) return la > ca;
|
|
3923
|
+
if (lb !== cb) return lb > cb;
|
|
3924
|
+
return lc > cc;
|
|
3925
|
+
}
|
|
3926
|
+
async function getAvailableUpdate(currentVersion) {
|
|
3927
|
+
const now = Date.now();
|
|
3928
|
+
const lastChecked = cache.get("lastChecked");
|
|
3929
|
+
let latest = cache.get("latestVersion");
|
|
3930
|
+
if (!latest || now - lastChecked > CHECK_INTERVAL_MS) {
|
|
3931
|
+
const fetched = await fetchLatestVersion();
|
|
3932
|
+
if (fetched) {
|
|
3933
|
+
latest = fetched;
|
|
3934
|
+
cache.set("latestVersion", fetched);
|
|
3935
|
+
cache.set("lastChecked", now);
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
return latest && isNewer(latest, currentVersion) ? latest : null;
|
|
3939
|
+
}
|
|
3940
|
+
function installUpdate() {
|
|
3941
|
+
return new Promise((resolve, reject) => {
|
|
3942
|
+
execFile("npm", ["install", "-g", PACKAGE_NAME], { shell: true }, (err, _stdout, stderr) => {
|
|
3943
|
+
if (err) {
|
|
3944
|
+
reject(new Error(stderr.trim() || err.message));
|
|
3945
|
+
} else {
|
|
3946
|
+
resolve(cache.get("latestVersion"));
|
|
3947
|
+
}
|
|
3948
|
+
});
|
|
3949
|
+
});
|
|
3950
|
+
}
|
|
3951
|
+
async function startAutoUpdate(currentVersion) {
|
|
3952
|
+
const latest = await Promise.race([
|
|
3953
|
+
getAvailableUpdate(currentVersion),
|
|
3954
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 2e3))
|
|
3955
|
+
]);
|
|
3956
|
+
if (!latest) return;
|
|
3957
|
+
console.log(
|
|
3958
|
+
chalk17.cyan("\n \u2191 Auto-updating to v" + latest + "\u2026") + chalk17.dim(" (running in background)\n")
|
|
3959
|
+
);
|
|
3960
|
+
installUpdate().then((installedVersion) => {
|
|
3961
|
+
console.log(
|
|
3962
|
+
"\n" + chalk17.green(" \u2714 Updated to v" + (installedVersion || latest)) + chalk17.dim(" \u2014 restart tch to use the new version\n") + chalk17.cyan(" You \u203A ")
|
|
3963
|
+
// redraw the prompt hint
|
|
3964
|
+
);
|
|
3965
|
+
}).catch((err) => {
|
|
3966
|
+
console.log(
|
|
3967
|
+
"\n" + chalk17.red(" \u2718 Auto-update failed: ") + chalk17.dim(err.message) + "\n" + chalk17.dim(" Run manually: npm install -g techunter\n") + chalk17.cyan(" You \u203A ")
|
|
3968
|
+
);
|
|
3969
|
+
});
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3048
3972
|
// src/index.ts
|
|
3049
3973
|
var _require = createRequire(import.meta.url);
|
|
3050
3974
|
var { version } = _require("../package.json");
|
|
@@ -3063,6 +3987,8 @@ var SLASH_NAMES = [
|
|
|
3063
3987
|
"/d",
|
|
3064
3988
|
"/edit",
|
|
3065
3989
|
"/e",
|
|
3990
|
+
"/move",
|
|
3991
|
+
"/mv",
|
|
3066
3992
|
"/review",
|
|
3067
3993
|
"/rv",
|
|
3068
3994
|
"/accept",
|
|
@@ -3071,6 +3997,8 @@ var SLASH_NAMES = [
|
|
|
3071
3997
|
"/me",
|
|
3072
3998
|
"/code",
|
|
3073
3999
|
"/c",
|
|
4000
|
+
"/wiki",
|
|
4001
|
+
"/w",
|
|
3074
4002
|
"/config",
|
|
3075
4003
|
"/cfg",
|
|
3076
4004
|
"/init"
|
|
@@ -3086,8 +4014,14 @@ var _rl = null;
|
|
|
3086
4014
|
function promptUser() {
|
|
3087
4015
|
return new Promise((resolve) => {
|
|
3088
4016
|
if (process.stdin.isPaused()) process.stdin.resume();
|
|
4017
|
+
if (process.stdin.isTTY) {
|
|
4018
|
+
try {
|
|
4019
|
+
process.stdin.setRawMode(true);
|
|
4020
|
+
} catch {
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
3089
4023
|
_rl.resume();
|
|
3090
|
-
_rl.question(
|
|
4024
|
+
_rl.question(chalk18.cyan("You") + chalk18.dim(" \u203A "), resolve);
|
|
3091
4025
|
});
|
|
3092
4026
|
}
|
|
3093
4027
|
var COMMANDS = [
|
|
@@ -3097,50 +4031,52 @@ var COMMANDS = [
|
|
|
3097
4031
|
{ cmd: "/new", alias: "/n", desc: "Create a new task interactively" },
|
|
3098
4032
|
{ cmd: "/close", alias: "/d", desc: "Close (delete) a task" },
|
|
3099
4033
|
{ cmd: "/edit", alias: "/e", desc: "Edit the title or description of a task" },
|
|
4034
|
+
{ cmd: "/move", alias: "/mv", desc: "Move your task under another task as a sub-task" },
|
|
3100
4035
|
{ cmd: "/submit", alias: "/s", desc: "Commit, create PR, and mark in-review" },
|
|
3101
4036
|
{ cmd: "/review", alias: "/rv", desc: "List tasks waiting for your approval" },
|
|
3102
4037
|
{ cmd: "/accept", alias: "/ac", desc: "Accept a reviewed task: merge PR and close issue" },
|
|
3103
4038
|
{ cmd: "/config", alias: "/cfg", desc: "Change settings (branch, repo, API keys)" },
|
|
3104
4039
|
{ cmd: "/init", desc: "Re-run setup wizard for this repo" },
|
|
3105
4040
|
{ cmd: "/status", alias: "/me", desc: "Show tasks assigned to you" },
|
|
3106
|
-
{ cmd: "/code", alias: "/c", desc: "Open Claude Code for the current task branch" }
|
|
4041
|
+
{ cmd: "/code", alias: "/c", desc: "Open Claude Code for the current task branch" },
|
|
4042
|
+
{ cmd: "/wiki", alias: "/w", desc: "Generate or refresh TECHUNTER.md project overview" }
|
|
3107
4043
|
];
|
|
3108
4044
|
function cmdHelp() {
|
|
3109
4045
|
console.log("");
|
|
3110
|
-
console.log(
|
|
3111
|
-
console.log(
|
|
4046
|
+
console.log(chalk18.bold(" Commands"));
|
|
4047
|
+
console.log(chalk18.dim(" \u2500".repeat(35)));
|
|
3112
4048
|
for (const { cmd, alias, desc } of COMMANDS) {
|
|
3113
|
-
const left = (cmd + (alias ? ` ${
|
|
3114
|
-
console.log(` ${
|
|
4049
|
+
const left = (cmd + (alias ? ` ${chalk18.dim(alias)}` : "")).padEnd(22);
|
|
4050
|
+
console.log(` ${chalk18.cyan(cmd)}${alias ? " " + chalk18.dim(alias) : ""}`.padEnd(30) + chalk18.dim(desc));
|
|
3115
4051
|
}
|
|
3116
|
-
console.log(
|
|
4052
|
+
console.log(chalk18.dim("\n Anything else is sent to the AI agent.\n"));
|
|
3117
4053
|
}
|
|
3118
4054
|
function printBanner(config) {
|
|
3119
4055
|
const { owner, repo } = config.github;
|
|
3120
|
-
const g =
|
|
3121
|
-
const b =
|
|
3122
|
-
const p =
|
|
4056
|
+
const g = chalk18.cyan;
|
|
4057
|
+
const b = chalk18.bold.white;
|
|
4058
|
+
const p = chalk18.yellow.bold;
|
|
3123
4059
|
console.log("");
|
|
3124
4060
|
console.log(" " + g("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
3125
4061
|
console.log(p("\u25C6") + b("\u2550\u2550\u2550") + g("\u256C") + b(" TECHUNTER ") + g("\u256C") + b("\u2550\u2550\u2550\u25B6"));
|
|
3126
4062
|
console.log(" " + g("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
3127
4063
|
console.log("");
|
|
3128
4064
|
console.log(
|
|
3129
|
-
" " +
|
|
4065
|
+
" " + chalk18.bold("Techunter") + chalk18.dim(` v${version}`) + chalk18.dim(" \xB7 ") + chalk18.cyan(getModel(config)) + chalk18.dim(" \xB7 ") + chalk18.dim(`${owner}/${repo}`)
|
|
3130
4066
|
);
|
|
3131
4067
|
console.log("");
|
|
3132
4068
|
}
|
|
3133
4069
|
async function initNewRepo(config, owner, repo) {
|
|
3134
4070
|
console.log("");
|
|
3135
|
-
console.log(
|
|
3136
|
-
console.log(
|
|
4071
|
+
console.log(chalk18.bold.cyan(` New repo detected: ${owner}/${repo}`));
|
|
4072
|
+
console.log(chalk18.dim(" Setting up Techunter for this repository...\n"));
|
|
3137
4073
|
const newConfig = {
|
|
3138
4074
|
...config,
|
|
3139
4075
|
github: { owner, repo }
|
|
3140
4076
|
};
|
|
3141
4077
|
setConfig({ github: newConfig.github });
|
|
3142
|
-
const
|
|
3143
|
-
const spinner =
|
|
4078
|
+
const ora17 = (await import("ora")).default;
|
|
4079
|
+
const spinner = ora17("Creating Techunter labels...").start();
|
|
3144
4080
|
try {
|
|
3145
4081
|
await ensureLabels(newConfig);
|
|
3146
4082
|
spinner.succeed("Labels ready");
|
|
@@ -3156,7 +4092,7 @@ async function main() {
|
|
|
3156
4092
|
try {
|
|
3157
4093
|
await configCommand();
|
|
3158
4094
|
} catch (err) {
|
|
3159
|
-
console.error(
|
|
4095
|
+
console.error(chalk18.red(`
|
|
3160
4096
|
Error: ${err.message}`));
|
|
3161
4097
|
process.exit(1);
|
|
3162
4098
|
}
|
|
@@ -3170,7 +4106,7 @@ Error: ${err.message}`));
|
|
|
3170
4106
|
await initCommand();
|
|
3171
4107
|
config = getConfig();
|
|
3172
4108
|
} catch (err) {
|
|
3173
|
-
console.error(
|
|
4109
|
+
console.error(chalk18.red(`
|
|
3174
4110
|
Setup failed: ${err.message}`));
|
|
3175
4111
|
process.exit(1);
|
|
3176
4112
|
return;
|
|
@@ -3188,11 +4124,13 @@ Setup failed: ${err.message}`));
|
|
|
3188
4124
|
}
|
|
3189
4125
|
}
|
|
3190
4126
|
} else if (!config.github.owner) {
|
|
3191
|
-
console.error(
|
|
4127
|
+
console.error(chalk18.red("\nNo git remote found and no repo configured. Run tch init."));
|
|
3192
4128
|
process.exit(1);
|
|
3193
4129
|
}
|
|
3194
4130
|
printBanner(config);
|
|
3195
|
-
console.log(
|
|
4131
|
+
console.log(chalk18.dim(" Type /help for commands, or describe what you want.\n"));
|
|
4132
|
+
startAutoUpdate(version).catch(() => {
|
|
4133
|
+
});
|
|
3196
4134
|
await printTaskList(config);
|
|
3197
4135
|
await printMyTasks(config);
|
|
3198
4136
|
_rl = readline.createInterface({
|
|
@@ -3202,11 +4140,11 @@ Setup failed: ${err.message}`));
|
|
|
3202
4140
|
terminal: true
|
|
3203
4141
|
});
|
|
3204
4142
|
_rl.on("close", () => {
|
|
3205
|
-
console.log(
|
|
4143
|
+
console.log(chalk18.gray("\nGoodbye!"));
|
|
3206
4144
|
process.exit(0);
|
|
3207
4145
|
});
|
|
3208
4146
|
_rl.on("SIGINT", () => {
|
|
3209
|
-
console.log(
|
|
4147
|
+
console.log(chalk18.gray("\nGoodbye!"));
|
|
3210
4148
|
process.exit(0);
|
|
3211
4149
|
});
|
|
3212
4150
|
const messages = [];
|
|
@@ -3223,7 +4161,7 @@ Setup failed: ${err.message}`));
|
|
|
3223
4161
|
break;
|
|
3224
4162
|
case "/refresh":
|
|
3225
4163
|
case "/r":
|
|
3226
|
-
await
|
|
4164
|
+
await run9({}, config);
|
|
3227
4165
|
break;
|
|
3228
4166
|
case "/pick":
|
|
3229
4167
|
case "/p": {
|
|
@@ -3231,7 +4169,7 @@ Setup failed: ${err.message}`));
|
|
|
3231
4169
|
const preselected = arg ? parseInt(arg, 10) : void 0;
|
|
3232
4170
|
const result = await run3({ issue_number: Number.isNaN(preselected) ? void 0 : preselected }, config);
|
|
3233
4171
|
if (result && result !== "Cancelled.") {
|
|
3234
|
-
console.log(
|
|
4172
|
+
console.log(chalk18.green(`
|
|
3235
4173
|
${result}
|
|
3236
4174
|
`));
|
|
3237
4175
|
}
|
|
@@ -3241,7 +4179,7 @@ Setup failed: ${err.message}`));
|
|
|
3241
4179
|
case "/new":
|
|
3242
4180
|
case "/n": {
|
|
3243
4181
|
const result = await run4({}, config);
|
|
3244
|
-
console.log(
|
|
4182
|
+
console.log(chalk18.green(`
|
|
3245
4183
|
${result}
|
|
3246
4184
|
`));
|
|
3247
4185
|
await printTaskList(config);
|
|
@@ -3259,7 +4197,18 @@ Setup failed: ${err.message}`));
|
|
|
3259
4197
|
const arg = userInput.slice(cmd.length).trim().replace(/^#/, "");
|
|
3260
4198
|
const preselected = arg ? parseInt(arg, 10) : void 0;
|
|
3261
4199
|
const result = await run11({ issue_number: Number.isNaN(preselected) ? void 0 : preselected }, config);
|
|
3262
|
-
console.log(
|
|
4200
|
+
console.log(chalk18.green(`
|
|
4201
|
+
${result}
|
|
4202
|
+
`));
|
|
4203
|
+
await printTaskList(config);
|
|
4204
|
+
break;
|
|
4205
|
+
}
|
|
4206
|
+
case "/move":
|
|
4207
|
+
case "/mv": {
|
|
4208
|
+
const arg = userInput.slice(cmd.length).trim().replace(/^#/, "");
|
|
4209
|
+
const preselected = arg ? parseInt(arg, 10) : void 0;
|
|
4210
|
+
const result = await run12({ issue_number: Number.isNaN(preselected) ? void 0 : preselected }, config);
|
|
4211
|
+
console.log(chalk18.green(`
|
|
3263
4212
|
${result}
|
|
3264
4213
|
`));
|
|
3265
4214
|
await printTaskList(config);
|
|
@@ -3268,7 +4217,7 @@ Setup failed: ${err.message}`));
|
|
|
3268
4217
|
case "/close":
|
|
3269
4218
|
case "/d": {
|
|
3270
4219
|
const result = await run2({}, config);
|
|
3271
|
-
console.log(
|
|
4220
|
+
console.log(chalk18.green(`
|
|
3272
4221
|
${result}
|
|
3273
4222
|
`));
|
|
3274
4223
|
await printTaskList(config);
|
|
@@ -3276,7 +4225,7 @@ Setup failed: ${err.message}`));
|
|
|
3276
4225
|
}
|
|
3277
4226
|
case "/review":
|
|
3278
4227
|
case "/rv": {
|
|
3279
|
-
const result = await
|
|
4228
|
+
const result = await run8({}, config);
|
|
3280
4229
|
console.log("\n" + renderMarkdown(result));
|
|
3281
4230
|
break;
|
|
3282
4231
|
}
|
|
@@ -3290,8 +4239,8 @@ Setup failed: ${err.message}`));
|
|
|
3290
4239
|
case "/ac": {
|
|
3291
4240
|
const arg = userInput.slice(cmd.length).trim().replace(/^#/, "");
|
|
3292
4241
|
const preselected = arg ? parseInt(arg, 10) : void 0;
|
|
3293
|
-
const result = await
|
|
3294
|
-
console.log(
|
|
4242
|
+
const result = await run6({ issue_number: Number.isNaN(preselected) ? void 0 : preselected }, config);
|
|
4243
|
+
console.log(chalk18.green(`
|
|
3295
4244
|
${result}
|
|
3296
4245
|
`));
|
|
3297
4246
|
await printTaskList(config);
|
|
@@ -3307,17 +4256,25 @@ Setup failed: ${err.message}`));
|
|
|
3307
4256
|
config = getConfig();
|
|
3308
4257
|
await printTaskList(config);
|
|
3309
4258
|
} catch (err) {
|
|
3310
|
-
console.error(
|
|
4259
|
+
console.error(chalk18.red(`
|
|
3311
4260
|
Init failed: ${err.message}
|
|
3312
4261
|
`));
|
|
3313
4262
|
}
|
|
3314
4263
|
break;
|
|
3315
4264
|
case "/code":
|
|
3316
4265
|
case "/c":
|
|
3317
|
-
await
|
|
4266
|
+
await run10({}, config);
|
|
3318
4267
|
break;
|
|
4268
|
+
case "/wiki":
|
|
4269
|
+
case "/w": {
|
|
4270
|
+
const result = await run13({}, config);
|
|
4271
|
+
console.log(chalk18.green(`
|
|
4272
|
+
${result}
|
|
4273
|
+
`));
|
|
4274
|
+
break;
|
|
4275
|
+
}
|
|
3319
4276
|
default:
|
|
3320
|
-
console.log(
|
|
4277
|
+
console.log(chalk18.yellow(` Unknown command: ${cmd} (try /help)`));
|
|
3321
4278
|
}
|
|
3322
4279
|
continue;
|
|
3323
4280
|
}
|
|
@@ -3325,10 +4282,10 @@ Init failed: ${err.message}
|
|
|
3325
4282
|
messages.push({ role: "user", content: userInput });
|
|
3326
4283
|
try {
|
|
3327
4284
|
const response = await runAgentLoop(config, messages);
|
|
3328
|
-
console.log("\n" +
|
|
4285
|
+
console.log("\n" + chalk18.green("Techunter:") + "\n" + renderMarkdown(response));
|
|
3329
4286
|
} catch (err) {
|
|
3330
4287
|
messages.splice(prevLength);
|
|
3331
|
-
console.error(
|
|
4288
|
+
console.error(chalk18.red(`
|
|
3332
4289
|
Error: ${err.message}
|
|
3333
4290
|
`));
|
|
3334
4291
|
}
|
|
@@ -3336,6 +4293,6 @@ Error: ${err.message}
|
|
|
3336
4293
|
}
|
|
3337
4294
|
}
|
|
3338
4295
|
main().catch((err) => {
|
|
3339
|
-
console.error(
|
|
4296
|
+
console.error(chalk18.red(`Fatal: ${err.message}`));
|
|
3340
4297
|
process.exit(1);
|
|
3341
4298
|
});
|