@jojonax/codex-copilot 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * GitHub 操作模块 - 通过 gh CLI 操作 PR 和 Review
3
+ */
4
+
5
+ import { execSync } from 'child_process';
6
+ import { log } from './logger.js';
7
+
8
+ function gh(cmd, cwd) {
9
+ return execSync(`gh ${cmd}`, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
10
+ }
11
+
12
+ function ghJSON(cmd, cwd) {
13
+ const output = gh(cmd, cwd);
14
+ return output ? JSON.parse(output) : null;
15
+ }
16
+
17
+ /**
18
+ * 检查 gh CLI 是否登录
19
+ */
20
+ export function checkGhAuth() {
21
+ try {
22
+ execSync('gh auth status', { encoding: 'utf-8', stdio: 'pipe' });
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * 创建 PR
31
+ */
32
+ export function createPR(cwd, { title, body, base = 'main', head }) {
33
+ const output = gh(
34
+ `pr create --title "${title}" --body "${body}" --base ${base} --head ${head}`,
35
+ cwd
36
+ );
37
+ // 从输出中提取 PR URL 和编号
38
+ const urlMatch = output.match(/https:\/\/github\.com\/.+\/pull\/(\d+)/);
39
+ if (urlMatch) {
40
+ return { url: urlMatch[0], number: parseInt(urlMatch[1]) };
41
+ }
42
+ // 如果已存在
43
+ const existingMatch = output.match(/already exists.+\/pull\/(\d+)/);
44
+ if (existingMatch) {
45
+ return { url: output, number: parseInt(existingMatch[1]) };
46
+ }
47
+ throw new Error(`创建 PR 失败: ${output}`);
48
+ }
49
+
50
+ /**
51
+ * 获取 PR 的 Review 列表
52
+ */
53
+ export function getReviews(cwd, prNumber) {
54
+ try {
55
+ return ghJSON(`api repos/{owner}/{repo}/pulls/${prNumber}/reviews`, cwd) || [];
56
+ } catch {
57
+ return [];
58
+ }
59
+ }
60
+
61
+ /**
62
+ * 获取 PR 的 Review 评论
63
+ */
64
+ export function getReviewComments(cwd, prNumber) {
65
+ try {
66
+ return ghJSON(`api repos/{owner}/{repo}/pulls/${prNumber}/comments`, cwd) || [];
67
+ } catch {
68
+ return [];
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 获取 PR 的 issue 评论(包含 bot 评论)
74
+ */
75
+ export function getIssueComments(cwd, prNumber) {
76
+ try {
77
+ return ghJSON(`api repos/{owner}/{repo}/issues/${prNumber}/comments`, cwd) || [];
78
+ } catch {
79
+ return [];
80
+ }
81
+ }
82
+
83
+ /**
84
+ * 检查最新 Review 状态
85
+ * @returns {'APPROVED' | 'CHANGES_REQUESTED' | 'COMMENTED' | 'PENDING' | null}
86
+ */
87
+ export function getLatestReviewState(cwd, prNumber) {
88
+ const reviews = getReviews(cwd, prNumber);
89
+ if (!reviews || reviews.length === 0) return null;
90
+
91
+ // 过滤掉 PENDING 和 DISMISSED
92
+ const active = reviews.filter(r => r.state !== 'PENDING' && r.state !== 'DISMISSED');
93
+ if (active.length === 0) return null;
94
+
95
+ return active[active.length - 1].state;
96
+ }
97
+
98
+ /**
99
+ * 合并 PR
100
+ */
101
+ export function mergePR(cwd, prNumber, method = 'squash') {
102
+ gh(`pr merge ${prNumber} --${method} --delete-branch`, cwd);
103
+ }
104
+
105
+ /**
106
+ * 收集所有 Review 意见为结构化文本
107
+ */
108
+ export function collectReviewFeedback(cwd, prNumber) {
109
+ const reviews = getReviews(cwd, prNumber);
110
+ const comments = getReviewComments(cwd, prNumber);
111
+ const issueComments = getIssueComments(cwd, prNumber);
112
+
113
+ let feedback = '';
114
+
115
+ // Review 总评
116
+ for (const r of reviews) {
117
+ if (r.body && r.body.trim()) {
118
+ feedback += `### Review (${r.state})\n${r.body}\n\n`;
119
+ }
120
+ }
121
+
122
+ // 行内评论
123
+ for (const c of comments) {
124
+ feedback += `### ${c.path}:L${c.line || c.original_line}\n${c.body}\n\n`;
125
+ }
126
+
127
+ // Bot 评论(Gemini Code Assist 等)
128
+ for (const c of issueComments) {
129
+ if (c.user?.type === 'Bot' || c.user?.login?.includes('bot')) {
130
+ feedback += `### Bot Review (${c.user.login})\n${c.body}\n\n`;
131
+ }
132
+ }
133
+
134
+ return feedback.trim();
135
+ }
136
+
137
+ export const github = {
138
+ checkGhAuth, createPR, getReviews, getReviewComments,
139
+ getIssueComments, getLatestReviewState, mergePR, collectReviewFeedback,
140
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Logger utility - 统一终端输出样式
3
+ */
4
+
5
+ const COLORS = {
6
+ reset: '\x1b[0m',
7
+ green: '\x1b[32m',
8
+ yellow: '\x1b[33m',
9
+ red: '\x1b[31m',
10
+ blue: '\x1b[34m',
11
+ cyan: '\x1b[36m',
12
+ dim: '\x1b[2m',
13
+ bold: '\x1b[1m',
14
+ };
15
+
16
+ export const log = {
17
+ info: (msg) => console.log(` ${COLORS.green}✔${COLORS.reset} ${msg}`),
18
+ warn: (msg) => console.log(` ${COLORS.yellow}⚠${COLORS.reset} ${msg}`),
19
+ error: (msg) => console.log(` ${COLORS.red}✖${COLORS.reset} ${msg}`),
20
+ step: (msg) => console.log(` ${COLORS.blue}▶${COLORS.reset} ${msg}`),
21
+ dim: (msg) => console.log(` ${COLORS.dim}${msg}${COLORS.reset}`),
22
+ title: (msg) => console.log(` ${COLORS.bold}${COLORS.cyan}${msg}${COLORS.reset}`),
23
+ blank: () => console.log(''),
24
+ };
25
+
26
+ /**
27
+ * 进度条显示
28
+ */
29
+ export function progressBar(current, total, label = '') {
30
+ const width = 30;
31
+ const filled = Math.round((current / total) * width);
32
+ const bar = '█'.repeat(filled) + '░'.repeat(width - filled);
33
+ const pct = Math.round((current / total) * 100);
34
+ console.log(` ${COLORS.cyan}[${bar}]${COLORS.reset} ${pct}% ${label}`);
35
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 交互式终端工具
3
+ */
4
+
5
+ import { createInterface } from 'readline';
6
+
7
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
8
+
9
+ /**
10
+ * 提问并等待用户输入
11
+ */
12
+ export function ask(question) {
13
+ return new Promise((resolve) => {
14
+ rl.question(` ${question} `, (answer) => {
15
+ resolve(answer.trim());
16
+ });
17
+ });
18
+ }
19
+
20
+ /**
21
+ * 是/否确认
22
+ */
23
+ export async function confirm(question, defaultYes = true) {
24
+ const hint = defaultYes ? '(Y/n)' : '(y/N)';
25
+ const answer = await ask(`${question} ${hint}:`);
26
+ if (!answer) return defaultYes;
27
+ return answer.toLowerCase().startsWith('y');
28
+ }
29
+
30
+ /**
31
+ * 从列表中选择
32
+ */
33
+ export async function select(question, choices) {
34
+ console.log(` ${question}`);
35
+ for (let i = 0; i < choices.length; i++) {
36
+ console.log(` ${i + 1}. ${choices[i].label}`);
37
+ }
38
+ const answer = await ask('请输入编号:');
39
+ const idx = parseInt(answer) - 1;
40
+ if (idx >= 0 && idx < choices.length) return choices[idx];
41
+ return choices[0]; // 默认第一个
42
+ }
43
+
44
+ /**
45
+ * 关闭 readline
46
+ */
47
+ export function closePrompt() {
48
+ rl.close();
49
+ }