techunter 0.1.1 → 0.1.3
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 +23 -11
- package/dist/index.js +308 -159
- package/dist/mcp.js +299 -104
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -21,9 +21,9 @@ __export(github_exports, {
|
|
|
21
21
|
ensureLabels: () => ensureLabels,
|
|
22
22
|
formatGuideAsMarkdown: () => formatGuideAsMarkdown,
|
|
23
23
|
getAuthenticatedUser: () => getAuthenticatedUser,
|
|
24
|
-
getBaseBranch: () => getBaseBranch,
|
|
25
24
|
getDefaultBranch: () => getDefaultBranch,
|
|
26
25
|
getTask: () => getTask,
|
|
26
|
+
isCollaborator: () => isCollaborator,
|
|
27
27
|
listComments: () => listComments,
|
|
28
28
|
listMyTasks: () => listMyTasks,
|
|
29
29
|
listTasks: () => listTasks,
|
|
@@ -50,6 +50,7 @@ function parseIssue(issue) {
|
|
|
50
50
|
title: issue.title,
|
|
51
51
|
body: issue.body ?? null,
|
|
52
52
|
state: issue.state,
|
|
53
|
+
author: issue.user?.login ?? null,
|
|
53
54
|
assignee: issue.assignee?.login ?? null,
|
|
54
55
|
labels: (issue.labels ?? []).map(
|
|
55
56
|
(l) => typeof l === "string" ? l : l.name ?? ""
|
|
@@ -226,6 +227,16 @@ async function getAuthenticatedUser(config) {
|
|
|
226
227
|
const { data } = await octokit.users.getAuthenticated();
|
|
227
228
|
return data.login;
|
|
228
229
|
}
|
|
230
|
+
async function isCollaborator(config, username) {
|
|
231
|
+
const octokit = createOctokit(config.githubToken);
|
|
232
|
+
const { owner, repo } = config.github;
|
|
233
|
+
try {
|
|
234
|
+
const { data } = await octokit.repos.getCollaboratorPermissionLevel({ owner, repo, username });
|
|
235
|
+
return data.permission === "admin" || data.permission === "write" || data.permission === "maintain";
|
|
236
|
+
} catch {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
229
240
|
async function listMyTasks(config, username) {
|
|
230
241
|
const octokit = createOctokit(config.githubToken);
|
|
231
242
|
const { owner, repo } = config.github;
|
|
@@ -297,10 +308,6 @@ async function getDefaultBranch(config) {
|
|
|
297
308
|
const { data } = await octokit.repos.get({ owner, repo });
|
|
298
309
|
return data.default_branch;
|
|
299
310
|
}
|
|
300
|
-
function getBaseBranch(config) {
|
|
301
|
-
if (config.github.baseBranch) return Promise.resolve(config.github.baseBranch);
|
|
302
|
-
return getDefaultBranch(config);
|
|
303
|
-
}
|
|
304
311
|
async function acceptTask(config, issueNumber) {
|
|
305
312
|
const octokit = createOctokit(config.githubToken);
|
|
306
313
|
const { owner, repo } = config.github;
|
|
@@ -360,9 +367,6 @@ async function getCurrentBranch() {
|
|
|
360
367
|
const summary = await git.branch();
|
|
361
368
|
return summary.current;
|
|
362
369
|
}
|
|
363
|
-
async function createAndSwitchBranch(name) {
|
|
364
|
-
await git.checkoutLocalBranch(name);
|
|
365
|
-
}
|
|
366
370
|
async function pushBranch(name) {
|
|
367
371
|
await git.push("origin", name, ["--set-upstream"]);
|
|
368
372
|
}
|
|
@@ -370,6 +374,61 @@ function makeBranchName(issueNumber, username) {
|
|
|
370
374
|
const slug = username.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "user";
|
|
371
375
|
return `task-${issueNumber}-${slug}`;
|
|
372
376
|
}
|
|
377
|
+
function makeWorkerBranchName(username) {
|
|
378
|
+
const slug = username.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "user";
|
|
379
|
+
return `worker-${slug}`;
|
|
380
|
+
}
|
|
381
|
+
async function getCurrentCommit() {
|
|
382
|
+
return (await git.revparse(["HEAD"])).trim();
|
|
383
|
+
}
|
|
384
|
+
async function switchToBranchOrCreate(name) {
|
|
385
|
+
try {
|
|
386
|
+
const branches = await git.branch(["-a"]);
|
|
387
|
+
const exists = Object.keys(branches.branches).some(
|
|
388
|
+
(b) => b === name || b === `remotes/origin/${name}`
|
|
389
|
+
);
|
|
390
|
+
if (exists) {
|
|
391
|
+
await git.checkout(name);
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
await git.checkoutLocalBranch(name);
|
|
395
|
+
return true;
|
|
396
|
+
} catch {
|
|
397
|
+
await git.checkoutLocalBranch(name);
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async function getDiffFromCommit(baseCommit) {
|
|
402
|
+
const status = await git.status();
|
|
403
|
+
const parts = [];
|
|
404
|
+
const fileLines = [
|
|
405
|
+
...status.modified.map((f) => ` M ${f}`),
|
|
406
|
+
...status.created.map((f) => ` A ${f}`),
|
|
407
|
+
...status.deleted.map((f) => ` D ${f}`),
|
|
408
|
+
...status.renamed.map((f) => ` R ${f.from} \u2192 ${f.to}`),
|
|
409
|
+
...status.not_added.map((f) => ` ? ${f}`)
|
|
410
|
+
];
|
|
411
|
+
if (fileLines.length > 0) {
|
|
412
|
+
parts.push("## Uncommitted changes\n" + fileLines.join("\n"));
|
|
413
|
+
const uncommitted = await git.diff(["HEAD"]);
|
|
414
|
+
if (uncommitted) {
|
|
415
|
+
const capped = uncommitted.length > 8e3 ? uncommitted.slice(0, 8e3) + "\n... (truncated)" : uncommitted;
|
|
416
|
+
parts.push("## Uncommitted diff\n```diff\n" + capped + "\n```");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const log = await git.log({ from: baseCommit, to: "HEAD" });
|
|
420
|
+
if (log.total > 0) {
|
|
421
|
+
const logLines = log.all.map((c) => ` ${c.hash.slice(0, 7)} ${c.message}`);
|
|
422
|
+
parts.push(`## Commits since task claimed (${log.total} total)
|
|
423
|
+
` + logLines.join("\n"));
|
|
424
|
+
const branchDiff = await git.diff([baseCommit, "HEAD"]);
|
|
425
|
+
if (branchDiff) {
|
|
426
|
+
const capped = branchDiff.length > 12e3 ? branchDiff.slice(0, 12e3) + "\n... (truncated)" : branchDiff;
|
|
427
|
+
parts.push("## Full diff since task claimed\n```diff\n" + capped + "\n```");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return parts.length > 0 ? parts.join("\n\n") : "No changes since task was claimed.";
|
|
431
|
+
}
|
|
373
432
|
async function findMergeBase(configuredBase) {
|
|
374
433
|
const candidates = configuredBase ? [`origin/${configuredBase}`, "origin/main", "origin/master"] : ["origin/main", "origin/master"];
|
|
375
434
|
const unique = [...new Set(candidates)];
|
|
@@ -428,6 +487,68 @@ async function stageAllAndCommit(message) {
|
|
|
428
487
|
await git.push("origin", branch, ["--set-upstream"]);
|
|
429
488
|
}
|
|
430
489
|
|
|
490
|
+
// src/lib/config.ts
|
|
491
|
+
import Conf from "conf";
|
|
492
|
+
import { z } from "zod";
|
|
493
|
+
var configSchema = z.object({
|
|
494
|
+
aiApiKey: z.string().min(1),
|
|
495
|
+
aiBaseUrl: z.string().optional(),
|
|
496
|
+
aiModel: z.string().optional(),
|
|
497
|
+
githubToken: z.string().min(1),
|
|
498
|
+
githubClientId: z.string().optional(),
|
|
499
|
+
github: z.object({
|
|
500
|
+
owner: z.string().min(1),
|
|
501
|
+
repo: z.string().min(1)
|
|
502
|
+
}),
|
|
503
|
+
taskState: z.object({
|
|
504
|
+
activeIssueNumber: z.number().optional(),
|
|
505
|
+
baseCommit: z.string().optional()
|
|
506
|
+
}).optional()
|
|
507
|
+
});
|
|
508
|
+
var store = new Conf({
|
|
509
|
+
projectName: "techunter",
|
|
510
|
+
defaults: {}
|
|
511
|
+
});
|
|
512
|
+
function getConfig() {
|
|
513
|
+
const raw = store.store;
|
|
514
|
+
const result = configSchema.safeParse(raw);
|
|
515
|
+
if (!result.success) {
|
|
516
|
+
throw new Error("Configuration is missing or invalid.");
|
|
517
|
+
}
|
|
518
|
+
return result.data;
|
|
519
|
+
}
|
|
520
|
+
function setConfig(partial) {
|
|
521
|
+
const current = store.store;
|
|
522
|
+
if (partial.github) {
|
|
523
|
+
current["github"] = {
|
|
524
|
+
...current["github"] ?? {},
|
|
525
|
+
...partial.github
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
if (partial.aiApiKey !== void 0) {
|
|
529
|
+
current["aiApiKey"] = partial.aiApiKey;
|
|
530
|
+
}
|
|
531
|
+
if (partial.aiBaseUrl !== void 0) {
|
|
532
|
+
current["aiBaseUrl"] = partial.aiBaseUrl;
|
|
533
|
+
}
|
|
534
|
+
if (partial.aiModel !== void 0) {
|
|
535
|
+
current["aiModel"] = partial.aiModel;
|
|
536
|
+
}
|
|
537
|
+
if (partial.githubToken !== void 0) {
|
|
538
|
+
current["githubToken"] = partial.githubToken;
|
|
539
|
+
}
|
|
540
|
+
if (partial.githubClientId !== void 0) {
|
|
541
|
+
current["githubClientId"] = partial.githubClientId;
|
|
542
|
+
}
|
|
543
|
+
if (partial.taskState !== void 0) {
|
|
544
|
+
current["taskState"] = {
|
|
545
|
+
...current["taskState"] ?? {},
|
|
546
|
+
...partial.taskState
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
store.store = current;
|
|
550
|
+
}
|
|
551
|
+
|
|
431
552
|
// src/lib/markdown.ts
|
|
432
553
|
import { marked } from "marked";
|
|
433
554
|
import { markedTerminal } from "marked-terminal";
|
|
@@ -635,7 +756,7 @@ async function runSubAgentLoop(config, systemPrompt, userMessage, toolNames) {
|
|
|
635
756
|
}
|
|
636
757
|
|
|
637
758
|
// src/tools/submit/prompts.ts
|
|
638
|
-
var REVIEWER_SYSTEM_PROMPT = "You are a concise code reviewer. 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.";
|
|
759
|
+
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.";
|
|
639
760
|
|
|
640
761
|
// src/tools/submit/reviewer.ts
|
|
641
762
|
async function reviewChanges(config, issueNumber, issue, diff) {
|
|
@@ -669,32 +790,41 @@ var definition = {
|
|
|
669
790
|
}
|
|
670
791
|
};
|
|
671
792
|
async function run(_input, config) {
|
|
672
|
-
const
|
|
673
|
-
const
|
|
674
|
-
if (!
|
|
675
|
-
return
|
|
793
|
+
const taskState = getConfig().taskState;
|
|
794
|
+
const issueNumber = taskState?.activeIssueNumber;
|
|
795
|
+
if (!issueNumber) {
|
|
796
|
+
return "No active task found. Claim a task first with /pick.";
|
|
676
797
|
}
|
|
677
|
-
const issueNumber = parseInt(match[1], 10);
|
|
678
798
|
let spinner = ora("Loading task and diff\u2026").start();
|
|
679
|
-
const
|
|
799
|
+
const diffPromise = taskState?.baseCommit ? getDiffFromCommit(taskState.baseCommit) : getDiff();
|
|
800
|
+
const [issue, diff, me] = await Promise.all([
|
|
680
801
|
getTask(config, issueNumber),
|
|
681
|
-
|
|
682
|
-
|
|
802
|
+
diffPromise,
|
|
803
|
+
getAuthenticatedUser(config)
|
|
683
804
|
]);
|
|
684
805
|
spinner.stop();
|
|
685
|
-
const
|
|
806
|
+
const workerBranch = makeWorkerBranchName(issue.author ?? me);
|
|
807
|
+
const branch = await getCurrentBranch();
|
|
808
|
+
const isSelfSubmit = issue.author !== null && issue.author === me;
|
|
686
809
|
let review = "";
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
810
|
+
if (!isSelfSubmit) {
|
|
811
|
+
const reviewSpinner = ora("Reviewing changes\u2026").start();
|
|
812
|
+
try {
|
|
813
|
+
review = await reviewChanges(config, issueNumber, issue, diff);
|
|
814
|
+
} catch (err) {
|
|
815
|
+
review = `(Review failed: ${err.message})`;
|
|
816
|
+
}
|
|
817
|
+
reviewSpinner.stop();
|
|
691
818
|
}
|
|
692
|
-
reviewSpinner.stop();
|
|
693
819
|
const divider = chalk5.dim("\u2500".repeat(70));
|
|
694
820
|
console.log("\n" + divider);
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
821
|
+
if (isSelfSubmit) {
|
|
822
|
+
console.log(chalk5.yellow(` Self-submit detected \u2014 AI review skipped.`));
|
|
823
|
+
} else {
|
|
824
|
+
console.log(chalk5.bold(` Review \u2014 task #${issueNumber} "${issue.title}"`));
|
|
825
|
+
console.log(divider);
|
|
826
|
+
console.log(renderMarkdown(review));
|
|
827
|
+
}
|
|
698
828
|
console.log(divider + "\n");
|
|
699
829
|
let shouldProceed;
|
|
700
830
|
try {
|
|
@@ -730,15 +860,15 @@ async function run(_input, config) {
|
|
|
730
860
|
spinner = ora("Creating pull request\u2026").start();
|
|
731
861
|
let prUrl;
|
|
732
862
|
try {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
issue.
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
);
|
|
863
|
+
const prBody = [
|
|
864
|
+
`Closes #${issueNumber}`,
|
|
865
|
+
issue.body ? `
|
|
866
|
+
${issue.body}` : "",
|
|
867
|
+
review ? `
|
|
868
|
+
## AI Review
|
|
869
|
+
${review}` : ""
|
|
870
|
+
].join("\n").trim();
|
|
871
|
+
prUrl = await createPR(config, issue.title, prBody, branch, workerBranch);
|
|
742
872
|
spinner.stop();
|
|
743
873
|
} catch (err) {
|
|
744
874
|
spinner.stop();
|
|
@@ -752,25 +882,31 @@ ${issue.body ?? ""}`.trim(),
|
|
|
752
882
|
spinner.stop();
|
|
753
883
|
return `PR created (${prUrl}) but failed to update label: ${err.message}`;
|
|
754
884
|
}
|
|
885
|
+
setConfig({ taskState: { activeIssueNumber: void 0, baseCommit: void 0 } });
|
|
755
886
|
return `Task #${issueNumber} submitted.
|
|
756
887
|
Commit: "${commitMessage.trim()}"
|
|
757
888
|
PR: ${prUrl}`;
|
|
758
889
|
}
|
|
759
890
|
async function execute(input, config) {
|
|
760
|
-
const
|
|
761
|
-
const
|
|
762
|
-
if (!
|
|
763
|
-
const
|
|
764
|
-
const [issue,
|
|
891
|
+
const taskState = getConfig().taskState;
|
|
892
|
+
const issueNumber = taskState?.activeIssueNumber;
|
|
893
|
+
if (!issueNumber) return "No active task found. Claim a task first.";
|
|
894
|
+
const diffPromise = taskState?.baseCommit ? getDiffFromCommit(taskState.baseCommit) : getDiff();
|
|
895
|
+
const [issue, diff, branch, me] = await Promise.all([
|
|
765
896
|
getTask(config, issueNumber),
|
|
766
|
-
|
|
767
|
-
|
|
897
|
+
diffPromise,
|
|
898
|
+
getCurrentBranch(),
|
|
899
|
+
getAuthenticatedUser(config)
|
|
768
900
|
]);
|
|
901
|
+
const workerBranch = makeWorkerBranchName(issue.author ?? me);
|
|
902
|
+
const isSelfSubmit = issue.author !== null && issue.author === me;
|
|
769
903
|
let review = "";
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
904
|
+
if (!isSelfSubmit) {
|
|
905
|
+
try {
|
|
906
|
+
review = await reviewChanges(config, issueNumber, issue, diff);
|
|
907
|
+
} catch (err) {
|
|
908
|
+
review = `(Review failed: ${err.message})`;
|
|
909
|
+
}
|
|
774
910
|
}
|
|
775
911
|
const commitMessage = input["commit_message"]?.trim() || `complete: ${issue.title}`;
|
|
776
912
|
try {
|
|
@@ -780,15 +916,15 @@ async function execute(input, config) {
|
|
|
780
916
|
}
|
|
781
917
|
let prUrl;
|
|
782
918
|
try {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
issue.
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
);
|
|
919
|
+
const prBody = [
|
|
920
|
+
`Closes #${issueNumber}`,
|
|
921
|
+
issue.body ? `
|
|
922
|
+
${issue.body}` : "",
|
|
923
|
+
review ? `
|
|
924
|
+
## AI Review
|
|
925
|
+
${review}` : ""
|
|
926
|
+
].join("\n").trim();
|
|
927
|
+
prUrl = await createPR(config, issue.title, prBody, branch, workerBranch);
|
|
792
928
|
} catch (err) {
|
|
793
929
|
return `Committed but PR creation failed: ${err.message}`;
|
|
794
930
|
}
|
|
@@ -982,23 +1118,42 @@ Finish or submit it before claiming a new one.`;
|
|
|
982
1118
|
let spinner = ora3(`Claiming #${issue.number}\u2026`).start();
|
|
983
1119
|
await claimTask(config, issue.number, me);
|
|
984
1120
|
spinner.stop();
|
|
985
|
-
const
|
|
986
|
-
spinner = ora3(`
|
|
1121
|
+
const workerBranch = makeWorkerBranchName(me);
|
|
1122
|
+
spinner = ora3(`Switching to ${workerBranch}\u2026`).start();
|
|
987
1123
|
try {
|
|
988
|
-
await
|
|
1124
|
+
const isNewWorker = await switchToBranchOrCreate(workerBranch);
|
|
989
1125
|
spinner.stop();
|
|
1126
|
+
if (isNewWorker) {
|
|
1127
|
+
spinner = ora3("Pushing worker branch\u2026").start();
|
|
1128
|
+
try {
|
|
1129
|
+
await pushBranch(workerBranch);
|
|
1130
|
+
spinner.stop();
|
|
1131
|
+
} catch {
|
|
1132
|
+
spinner.warn("Could not push worker branch");
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
990
1135
|
} catch {
|
|
991
|
-
spinner.warn(`Could not
|
|
1136
|
+
spinner.warn(`Could not switch to ${workerBranch}`);
|
|
992
1137
|
}
|
|
993
|
-
|
|
1138
|
+
const branch = makeBranchName(issue.number, me);
|
|
1139
|
+
spinner = ora3(`Creating task branch ${branch}\u2026`).start();
|
|
994
1140
|
try {
|
|
995
|
-
await
|
|
1141
|
+
await switchToBranchOrCreate(branch);
|
|
996
1142
|
spinner.stop();
|
|
1143
|
+
spinner = ora3("Pushing task branch\u2026").start();
|
|
1144
|
+
try {
|
|
1145
|
+
await pushBranch(branch);
|
|
1146
|
+
spinner.stop();
|
|
1147
|
+
} catch {
|
|
1148
|
+
spinner.warn("Could not push task branch");
|
|
1149
|
+
}
|
|
997
1150
|
} catch {
|
|
998
|
-
spinner.warn(
|
|
1151
|
+
spinner.warn(`Could not create branch ${branch}`);
|
|
999
1152
|
}
|
|
1153
|
+
const baseCommit = await getCurrentCommit();
|
|
1154
|
+
setConfig({ taskState: { activeIssueNumber: issue.number, baseCommit } });
|
|
1000
1155
|
console.log(chalk6.green(`
|
|
1001
|
-
Claimed! Branch: ${branch}
|
|
1156
|
+
Claimed! Branch: ${branch} (base: ${baseCommit.slice(0, 7)})
|
|
1002
1157
|
`));
|
|
1003
1158
|
let openClaude;
|
|
1004
1159
|
try {
|
|
@@ -1051,16 +1206,29 @@ async function execute3(input, config) {
|
|
|
1051
1206
|
} catch (err) {
|
|
1052
1207
|
return `Error claiming task: ${err.message}`;
|
|
1053
1208
|
}
|
|
1209
|
+
const workerBranch = makeWorkerBranchName(me);
|
|
1210
|
+
try {
|
|
1211
|
+
const isNewWorker = await switchToBranchOrCreate(workerBranch);
|
|
1212
|
+
if (isNewWorker) {
|
|
1213
|
+
try {
|
|
1214
|
+
await pushBranch(workerBranch);
|
|
1215
|
+
} catch {
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1054
1220
|
const branch = makeBranchName(issueNumber, me);
|
|
1055
1221
|
try {
|
|
1056
|
-
await
|
|
1222
|
+
await switchToBranchOrCreate(branch);
|
|
1057
1223
|
} catch {
|
|
1058
1224
|
}
|
|
1059
1225
|
try {
|
|
1060
1226
|
await pushBranch(branch);
|
|
1061
1227
|
} catch {
|
|
1062
1228
|
}
|
|
1063
|
-
|
|
1229
|
+
const baseCommit = await getCurrentCommit();
|
|
1230
|
+
setConfig({ taskState: { activeIssueNumber: issueNumber, baseCommit } });
|
|
1231
|
+
return `Task #${issueNumber} claimed. Branch: ${branch} (base commit: ${baseCommit.slice(0, 7)})`;
|
|
1064
1232
|
}
|
|
1065
1233
|
return `Unknown action: ${action}`;
|
|
1066
1234
|
}
|
|
@@ -1151,6 +1319,20 @@ var definition4 = {
|
|
|
1151
1319
|
}
|
|
1152
1320
|
};
|
|
1153
1321
|
async function run4(input, config) {
|
|
1322
|
+
const authSpinner = ora4("Checking permissions\u2026").start();
|
|
1323
|
+
let me;
|
|
1324
|
+
let allowed;
|
|
1325
|
+
try {
|
|
1326
|
+
me = await getAuthenticatedUser(config);
|
|
1327
|
+
allowed = await isCollaborator(config, me);
|
|
1328
|
+
authSpinner.stop();
|
|
1329
|
+
} catch (err) {
|
|
1330
|
+
authSpinner.stop();
|
|
1331
|
+
return `Error checking permissions: ${err.message}`;
|
|
1332
|
+
}
|
|
1333
|
+
if (!allowed) {
|
|
1334
|
+
return `Permission denied: only repository collaborators can create tasks.`;
|
|
1335
|
+
}
|
|
1154
1336
|
let title = input["title"]?.trim();
|
|
1155
1337
|
if (!title) {
|
|
1156
1338
|
try {
|
|
@@ -1248,6 +1430,10 @@ async function run4(input, config) {
|
|
|
1248
1430
|
return `Created #${issueNumber} "${issueTitle}" \u2014 ${htmlUrl}`;
|
|
1249
1431
|
}
|
|
1250
1432
|
async function execute4(input, config) {
|
|
1433
|
+
const me = await getAuthenticatedUser(config);
|
|
1434
|
+
if (!await isCollaborator(config, me)) {
|
|
1435
|
+
return `Permission denied: only repository collaborators can create tasks.`;
|
|
1436
|
+
}
|
|
1251
1437
|
const title = input["title"].trim();
|
|
1252
1438
|
const feedback = input["feedback"];
|
|
1253
1439
|
let guide = await generateGuide(config, title);
|
|
@@ -1466,6 +1652,13 @@ var definition9 = {
|
|
|
1466
1652
|
};
|
|
1467
1653
|
async function run9(input, config) {
|
|
1468
1654
|
const issueNumber = input["issue_number"];
|
|
1655
|
+
const [me, issue] = await Promise.all([
|
|
1656
|
+
getAuthenticatedUser(config),
|
|
1657
|
+
getTask(config, issueNumber)
|
|
1658
|
+
]);
|
|
1659
|
+
if (issue.author && issue.author !== me) {
|
|
1660
|
+
return `Permission denied: only the task author (@${issue.author}) can reject task #${issueNumber}.`;
|
|
1661
|
+
}
|
|
1469
1662
|
let feedback;
|
|
1470
1663
|
try {
|
|
1471
1664
|
feedback = await promptInput3({
|
|
@@ -1535,6 +1728,13 @@ async function run9(input, config) {
|
|
|
1535
1728
|
async function execute9(input, config) {
|
|
1536
1729
|
const issueNumber = input["issue_number"];
|
|
1537
1730
|
const feedback = input["feedback"];
|
|
1731
|
+
const [me, issue] = await Promise.all([
|
|
1732
|
+
getAuthenticatedUser(config),
|
|
1733
|
+
getTask(config, issueNumber)
|
|
1734
|
+
]);
|
|
1735
|
+
if (issue.author && issue.author !== me) {
|
|
1736
|
+
return `Permission denied: only the task author (@${issue.author}) can reject task #${issueNumber}.`;
|
|
1737
|
+
}
|
|
1538
1738
|
let comment;
|
|
1539
1739
|
try {
|
|
1540
1740
|
comment = await generateRejectionComment(config, issueNumber, feedback);
|
|
@@ -1574,7 +1774,7 @@ var definition10 = {
|
|
|
1574
1774
|
type: "function",
|
|
1575
1775
|
function: {
|
|
1576
1776
|
name: "accept",
|
|
1577
|
-
description: "Accept an in-review task: merges the PR into
|
|
1777
|
+
description: "Accept an in-review task: merges the PR into your worker branch and closes the issue.",
|
|
1578
1778
|
parameters: {
|
|
1579
1779
|
type: "object",
|
|
1580
1780
|
properties: {
|
|
@@ -1587,15 +1787,15 @@ var definition10 = {
|
|
|
1587
1787
|
async function run10(input, config) {
|
|
1588
1788
|
let issueNumber = input["issue_number"];
|
|
1589
1789
|
if (!issueNumber) {
|
|
1590
|
-
const
|
|
1790
|
+
const spinner3 = ora8("Loading tasks for review\u2026").start();
|
|
1591
1791
|
let tasks;
|
|
1592
1792
|
let me;
|
|
1593
1793
|
try {
|
|
1594
1794
|
me = await getAuthenticatedUser(config);
|
|
1595
1795
|
tasks = await listTasksForReview(config, me);
|
|
1596
|
-
|
|
1796
|
+
spinner3.stop();
|
|
1597
1797
|
} catch (err) {
|
|
1598
|
-
|
|
1798
|
+
spinner3.stop();
|
|
1599
1799
|
return `Error: ${err.message}`;
|
|
1600
1800
|
}
|
|
1601
1801
|
if (tasks.length === 0) return "No tasks pending review.";
|
|
@@ -1611,11 +1811,27 @@ async function run10(input, config) {
|
|
|
1611
1811
|
return "Cancelled.";
|
|
1612
1812
|
}
|
|
1613
1813
|
}
|
|
1614
|
-
const
|
|
1814
|
+
const spinner2 = ora8("Verifying permissions\u2026").start();
|
|
1815
|
+
let me2;
|
|
1816
|
+
let issue;
|
|
1817
|
+
try {
|
|
1818
|
+
[me2, issue] = await Promise.all([
|
|
1819
|
+
getAuthenticatedUser(config),
|
|
1820
|
+
getTask(config, issueNumber)
|
|
1821
|
+
]);
|
|
1822
|
+
spinner2.stop();
|
|
1823
|
+
} catch (err) {
|
|
1824
|
+
spinner2.stop();
|
|
1825
|
+
return `Error: ${err.message}`;
|
|
1826
|
+
}
|
|
1827
|
+
if (issue.author && issue.author !== me2) {
|
|
1828
|
+
return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
|
|
1829
|
+
}
|
|
1830
|
+
const targetBranch = makeWorkerBranchName(me2);
|
|
1615
1831
|
let confirmed;
|
|
1616
1832
|
try {
|
|
1617
1833
|
confirmed = await select6({
|
|
1618
|
-
message: `Merge PR for #${issueNumber} into ${chalk9.cyan(
|
|
1834
|
+
message: `Merge PR for #${issueNumber} into ${chalk9.cyan(targetBranch)} and close issue?`,
|
|
1619
1835
|
choices: [
|
|
1620
1836
|
{ name: "Yes, accept", value: true },
|
|
1621
1837
|
{ name: "Cancel", value: false }
|
|
@@ -1628,9 +1844,9 @@ async function run10(input, config) {
|
|
|
1628
1844
|
const spinner = ora8(`Merging PR for #${issueNumber}\u2026`).start();
|
|
1629
1845
|
try {
|
|
1630
1846
|
const result = await acceptTask(config, issueNumber);
|
|
1631
|
-
spinner.succeed(`PR #${result.prNumber} merged into ${
|
|
1847
|
+
spinner.succeed(`PR #${result.prNumber} merged into ${targetBranch}`);
|
|
1632
1848
|
return `Task #${issueNumber} accepted.
|
|
1633
|
-
PR #${result.prNumber} merged \u2192 ${
|
|
1849
|
+
PR #${result.prNumber} merged \u2192 ${targetBranch}
|
|
1634
1850
|
Issue closed.`;
|
|
1635
1851
|
} catch (err) {
|
|
1636
1852
|
spinner.fail("Failed");
|
|
@@ -1639,13 +1855,20 @@ Issue closed.`;
|
|
|
1639
1855
|
}
|
|
1640
1856
|
async function execute10(input, config) {
|
|
1641
1857
|
const issueNumber = input["issue_number"];
|
|
1858
|
+
const [me, issue] = await Promise.all([
|
|
1859
|
+
getAuthenticatedUser(config),
|
|
1860
|
+
getTask(config, issueNumber)
|
|
1861
|
+
]);
|
|
1862
|
+
if (issue.author && issue.author !== me) {
|
|
1863
|
+
return `Permission denied: only the task author (@${issue.author}) can accept task #${issueNumber}.`;
|
|
1864
|
+
}
|
|
1865
|
+
const targetBranch = makeWorkerBranchName(me);
|
|
1642
1866
|
const spinner = ora8(`Merging PR for #${issueNumber}\u2026`).start();
|
|
1643
1867
|
try {
|
|
1644
1868
|
const result = await acceptTask(config, issueNumber);
|
|
1645
1869
|
spinner.stop();
|
|
1646
|
-
const baseBranch = config.github.baseBranch ?? "main";
|
|
1647
1870
|
return `Task #${issueNumber} accepted.
|
|
1648
|
-
PR #${result.prNumber} merged \u2192 ${
|
|
1871
|
+
PR #${result.prNumber} merged \u2192 ${targetBranch}
|
|
1649
1872
|
Issue closed.`;
|
|
1650
1873
|
} catch (err) {
|
|
1651
1874
|
spinner.stop();
|
|
@@ -1873,7 +2096,7 @@ var definition15 = {
|
|
|
1873
2096
|
async function execute15(_input, _config) {
|
|
1874
2097
|
const spinner = ora11("Reading git diff...").start();
|
|
1875
2098
|
try {
|
|
1876
|
-
const diff = await getDiff(
|
|
2099
|
+
const diff = await getDiff();
|
|
1877
2100
|
spinner.stop();
|
|
1878
2101
|
return diff;
|
|
1879
2102
|
} catch (err) {
|
|
@@ -2240,34 +2463,6 @@ var toolModules = [
|
|
|
2240
2463
|
ask_user_exports
|
|
2241
2464
|
];
|
|
2242
2465
|
|
|
2243
|
-
// src/lib/config.ts
|
|
2244
|
-
import Conf from "conf";
|
|
2245
|
-
import { z } from "zod";
|
|
2246
|
-
var configSchema = z.object({
|
|
2247
|
-
aiApiKey: z.string().min(1),
|
|
2248
|
-
aiBaseUrl: z.string().optional(),
|
|
2249
|
-
aiModel: z.string().optional(),
|
|
2250
|
-
githubToken: z.string().min(1),
|
|
2251
|
-
githubClientId: z.string().optional(),
|
|
2252
|
-
github: z.object({
|
|
2253
|
-
owner: z.string().min(1),
|
|
2254
|
-
repo: z.string().min(1),
|
|
2255
|
-
baseBranch: z.string().optional()
|
|
2256
|
-
})
|
|
2257
|
-
});
|
|
2258
|
-
var store = new Conf({
|
|
2259
|
-
projectName: "techunter",
|
|
2260
|
-
defaults: {}
|
|
2261
|
-
});
|
|
2262
|
-
function getConfig() {
|
|
2263
|
-
const raw = store.store;
|
|
2264
|
-
const result = configSchema.safeParse(raw);
|
|
2265
|
-
if (!result.success) {
|
|
2266
|
-
throw new Error("Configuration is missing or invalid.");
|
|
2267
|
-
}
|
|
2268
|
-
return result.data;
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
2466
|
// src/mcp.ts
|
|
2272
2467
|
var tools = toolModules.filter((m) => m.definition.function.name !== "ask_user");
|
|
2273
2468
|
var server = new Server(
|