@d5render/cli 0.1.41 → 0.1.44
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/.skills/{review → code-review}/SKILL.md +1 -1
- package/.skills/devops/README.md +1 -1
- package/CHANGELOG.md +5 -2
- package/README.md +1 -1
- package/bin/copilot.js +106 -95
- package/bin/d5cli +37 -41
- package/package.json +5 -3
- /package/.github/instructions/{review.instructions.md → code-review.instructions.md} +0 -0
- /package/.skills/{review → code-review}/version +0 -0
|
@@ -3,7 +3,7 @@ name: code-review
|
|
|
3
3
|
description: When the task is code-review, please follow this document to proceed the work.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
1. read file [review.instructions.md](./review.instructions.md).
|
|
6
|
+
1. read file [code-review.instructions.md](./code-review.instructions.md).
|
|
7
7
|
2. find all Markdown resumes within the project and their basic understanding of the project.
|
|
8
8
|
3. integrate your understanding of code-review to process task.
|
|
9
9
|
4. the merge request is only for change reference and is not the main objective of the review; therefore, it does not require in-depth analysis.
|
package/.skills/devops/README.md
CHANGED
|
@@ -10,7 +10,7 @@ CI MCP 的入口是 [server.ts](.\copilot\server\index.ts) 会经历一轮打包
|
|
|
10
10
|
|
|
11
11
|
开发的一些注意点:
|
|
12
12
|
|
|
13
|
-
1. [
|
|
13
|
+
1. [config.ts](.\copilot\server\config.ts) 属于公共配置,有需要可以放这里
|
|
14
14
|
2. [vscode](.\vscode\index.ts) 插件的 client 需要单独开发
|
|
15
15
|
3. 本地功能调试 gpt-5-mini 食用最佳🤣,效果测试建议切回当前模型(开发环境已内置)
|
|
16
16
|
4. 返回 Promise 之后会丢失开发过程中的类型支持,registerTool 调用时可先不写 Promise 看 MCP Server 支持的返回参数
|
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
package/bin/copilot.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import process$1, { argv } from "node:process";
|
|
1
|
+
import process$1, { argv, platform } from "node:process";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
@@ -18809,7 +18809,6 @@ const getHash = "hash";
|
|
|
18809
18809
|
const noMandatory = "dont't call under non-mandatory conditions";
|
|
18810
18810
|
const file = "bin/copilot.js";
|
|
18811
18811
|
const RUNTIME_CWD = join(dirname(fileURLToPath(import.meta.url)), "../");
|
|
18812
|
-
console.log("RUNTIME_CWD:", RUNTIME_CWD);
|
|
18813
18812
|
const serveFile = join(RUNTIME_CWD, file);
|
|
18814
18813
|
const envJson = buildEnv();
|
|
18815
18814
|
const envUsed = {
|
|
@@ -18845,7 +18844,7 @@ const tools = [
|
|
|
18845
18844
|
"--deny-tool",
|
|
18846
18845
|
"github-mcp-server"
|
|
18847
18846
|
];
|
|
18848
|
-
tools.push("--model", "
|
|
18847
|
+
if (platform === "linux") tools.push("--model", "gemini-3-pro-preview");
|
|
18849
18848
|
function toEnv(key, defaultValue) {
|
|
18850
18849
|
return envJson[key] || process.env[key] || defaultValue;
|
|
18851
18850
|
}
|
|
@@ -18866,19 +18865,29 @@ function buildHeaders() {
|
|
|
18866
18865
|
else headers.set("Authorization", `Bearer ${GITLAB_TOKEN}`);
|
|
18867
18866
|
return headers;
|
|
18868
18867
|
}
|
|
18868
|
+
function apiHost() {
|
|
18869
|
+
const { CI_PROJECT_ID, CI_SERVER_URL } = envUsed;
|
|
18870
|
+
if (CI_PROJECT_ID && CI_SERVER_URL) return `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}`;
|
|
18871
|
+
}
|
|
18872
|
+
function apiProject() {
|
|
18873
|
+
const { CI_PROJECT_PATH, CI_SERVER_URL } = envUsed;
|
|
18874
|
+
if (CI_PROJECT_PATH && CI_SERVER_URL) return `${CI_SERVER_URL}/${CI_PROJECT_PATH}`;
|
|
18875
|
+
}
|
|
18869
18876
|
const pipelineMerge = () => {
|
|
18870
|
-
const
|
|
18871
|
-
|
|
18877
|
+
const api = apiHost();
|
|
18878
|
+
const { CI_MERGE_REQUEST_IID } = envUsed;
|
|
18879
|
+
if (CI_MERGE_REQUEST_IID && api) return `${api}/merge_requests/${CI_MERGE_REQUEST_IID}`;
|
|
18872
18880
|
};
|
|
18873
18881
|
const visitCommit = () => {
|
|
18874
|
-
const
|
|
18875
|
-
if (
|
|
18882
|
+
const api = apiProject();
|
|
18883
|
+
if (api) return `${api}/-/commit`;
|
|
18876
18884
|
};
|
|
18877
18885
|
function visitPipeline(mid) {
|
|
18878
|
-
const
|
|
18879
|
-
if (!
|
|
18886
|
+
const api = apiProject();
|
|
18887
|
+
if (!api) return {};
|
|
18888
|
+
const { CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
|
|
18880
18889
|
if (CI_MERGE_REQUEST_IID) return {
|
|
18881
|
-
url: `${
|
|
18890
|
+
url: `${api}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
|
|
18882
18891
|
type: "merge_request"
|
|
18883
18892
|
};
|
|
18884
18893
|
if (CI_COMMIT_SHA) return {
|
|
@@ -18888,45 +18897,29 @@ function visitPipeline(mid) {
|
|
|
18888
18897
|
return {};
|
|
18889
18898
|
}
|
|
18890
18899
|
const commits = () => {
|
|
18891
|
-
const
|
|
18892
|
-
if (
|
|
18900
|
+
const api = apiHost();
|
|
18901
|
+
if (api) return `${api}/repository/commits`;
|
|
18893
18902
|
};
|
|
18894
18903
|
|
|
18895
18904
|
//#endregion
|
|
18896
18905
|
//#region packages/gitlab/commit.ts
|
|
18897
18906
|
const getCommits = () => {
|
|
18898
|
-
const { CI_COMMIT_SHA, CI_COMMIT_BEFORE_SHA } = envUsed;
|
|
18899
18907
|
const url = pipelineMerge();
|
|
18900
18908
|
if (url) return fetch(`${url}/commits`, { headers: buildHeaders() }).then((res) => {
|
|
18901
|
-
if (!res.ok) throw new Error("");
|
|
18909
|
+
if (!res.ok) throw new Error("请求GitLab API失败" + res.statusText);
|
|
18902
18910
|
return res.json();
|
|
18903
|
-
}).then((commits$1) => {
|
|
18904
|
-
|
|
18905
|
-
commits
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
短ID: commit.short_id,
|
|
18909
|
-
标题: commit.title,
|
|
18910
|
-
消息: commit.message?.substring(0, 50),
|
|
18911
|
-
作者姓名: commit.author_name,
|
|
18912
|
-
作者邮箱: commit.author_email,
|
|
18913
|
-
提交日期: commit.authored_date,
|
|
18914
|
-
提交者姓名: commit.committer_name,
|
|
18915
|
-
提交者邮箱: commit.committer_email
|
|
18916
|
-
});
|
|
18917
|
-
});
|
|
18918
|
-
return { content: [{
|
|
18919
|
-
type: "text",
|
|
18920
|
-
description: "commits from `merge pipeline`",
|
|
18921
|
-
text: `${commits$1.map((commit) => commit.id).join(",")}, the above review is from \`merge pipeline\` commits.`
|
|
18922
|
-
}] };
|
|
18923
|
-
}).catch((error) => ({
|
|
18911
|
+
}).then((commits$1) => ({ content: [{
|
|
18912
|
+
type: "text",
|
|
18913
|
+
description: "commits from `merge pipeline`",
|
|
18914
|
+
text: `${commits$1.map((commit) => commit.id).join(",")}, the above review is from \`merge pipeline\` commits.`
|
|
18915
|
+
}] })).catch((error) => ({
|
|
18924
18916
|
content: [{
|
|
18925
18917
|
type: "text",
|
|
18926
|
-
text: "error
|
|
18918
|
+
text: "error " + (error.message || "请求GitLab API失败,请检查网络连接或访问权限")
|
|
18927
18919
|
}],
|
|
18928
18920
|
isError: true
|
|
18929
18921
|
}));
|
|
18922
|
+
const { CI_COMMIT_SHA, CI_COMMIT_BEFORE_SHA } = envUsed;
|
|
18930
18923
|
if (CI_COMMIT_SHA && CI_COMMIT_BEFORE_SHA) return { content: [{
|
|
18931
18924
|
type: "text",
|
|
18932
18925
|
description: "commit from push pipeline",
|
|
@@ -18982,7 +18975,8 @@ function commonHeaders() {
|
|
|
18982
18975
|
const getIssue = (request) => {
|
|
18983
18976
|
const { key } = request;
|
|
18984
18977
|
const { JIRA_BASE_URL } = envUsed;
|
|
18985
|
-
return fetch(`${JIRA_BASE_URL}/rest/api/latest/issue/${key}`, { headers: commonHeaders() }).then((res) => res.json()).then((
|
|
18978
|
+
return fetch(`${JIRA_BASE_URL}/rest/api/latest/issue/${key}`, { headers: commonHeaders() }).then((res) => res.json()).then((res) => {
|
|
18979
|
+
const { fields = {}, key: key$1 = "" } = res;
|
|
18986
18980
|
const { comment = {}, description = "", summary = "", priority = {} } = fields;
|
|
18987
18981
|
const { comments = [] } = comment;
|
|
18988
18982
|
return { content: [{
|
|
@@ -19096,22 +19090,64 @@ const schemaGitlabPipeline = "gitlab_pipeline";
|
|
|
19096
19090
|
const schemaGitlabCommit = "gitlab_commit";
|
|
19097
19091
|
const schemaDingdingTalk = "dingding_talk";
|
|
19098
19092
|
function distReports(input) {
|
|
19093
|
+
const { CI_PROJECT_NAME = "", CI_COMMIT_SHA = "", JIRA_BASE_URL, CI_MERGE_REQUEST_IID, DINGTALK_WEBHOOK } = envUsed;
|
|
19094
|
+
const { title = "代码审查报告", summary = [], issues = [], jiras = [], risks = [], suggestions = [] } = input;
|
|
19095
|
+
const mergeURL = pipelineMerge();
|
|
19096
|
+
const commitsURL = commits();
|
|
19097
|
+
let gitlabReportMessage = `## 📖 ${title}\n`;
|
|
19098
|
+
const commitComments = [];
|
|
19099
|
+
let dingdingReportMessage = "";
|
|
19099
19100
|
const gitlabReport = {
|
|
19100
19101
|
title: schemaGitlabPipeline,
|
|
19101
|
-
report: [() =>
|
|
19102
|
+
report: [() => {
|
|
19103
|
+
if (mergeURL) return fetch(`${mergeURL}/notes`, {
|
|
19104
|
+
method: "POST",
|
|
19105
|
+
headers: buildHeaders(),
|
|
19106
|
+
body: JSON.stringify({ body: gitlabReportMessage })
|
|
19107
|
+
}).then((res) => res.json()).then((res) => {
|
|
19108
|
+
lastComment.value = res;
|
|
19109
|
+
});
|
|
19110
|
+
else if (commitsURL) {
|
|
19111
|
+
const pipeURL = `${commitsURL}/${CI_COMMIT_SHA}`;
|
|
19112
|
+
return fetch(`${pipeURL}/comments`, {
|
|
19113
|
+
method: "POST",
|
|
19114
|
+
headers: buildHeaders(),
|
|
19115
|
+
body: JSON.stringify({
|
|
19116
|
+
note: gitlabReportMessage,
|
|
19117
|
+
line_type: "new"
|
|
19118
|
+
})
|
|
19119
|
+
}).then(() => fetch(`${pipeURL}/discussions`, {
|
|
19120
|
+
method: "GET",
|
|
19121
|
+
headers: buildHeaders()
|
|
19122
|
+
})).then((res) => res.json()).then((res) => {
|
|
19123
|
+
const { notes = [] } = res[res.length - 1] || {};
|
|
19124
|
+
lastComment.value = notes[notes.length - 1] || {};
|
|
19125
|
+
});
|
|
19126
|
+
}
|
|
19127
|
+
return Promise.resolve(void 0);
|
|
19128
|
+
}]
|
|
19102
19129
|
};
|
|
19103
19130
|
const gitlabCommitReport = {
|
|
19104
19131
|
title: schemaGitlabCommit,
|
|
19105
|
-
report: [() => Promise.resolve(
|
|
19132
|
+
report: [() => Promise.resolve()]
|
|
19106
19133
|
};
|
|
19107
19134
|
const dingdingReport = {
|
|
19108
19135
|
title: schemaDingdingTalk,
|
|
19109
|
-
report: [() =>
|
|
19136
|
+
report: [() => {
|
|
19137
|
+
if (!DINGTALK_WEBHOOK) return Promise.resolve(void 0);
|
|
19138
|
+
return sendding(title, dingdingReportMessage).then((response) => {
|
|
19139
|
+
if (!response.ok) throw new Error(response.statusText);
|
|
19140
|
+
return response.json();
|
|
19141
|
+
}).then((res) => {
|
|
19142
|
+
if (res.errcode === 0) return;
|
|
19143
|
+
if (res.errcode === 31e4) return;
|
|
19144
|
+
throw new Error("Post comments to DingTalk with error code:" + res.errcode + ", message: " + res.errmsg);
|
|
19145
|
+
});
|
|
19146
|
+
}]
|
|
19110
19147
|
};
|
|
19111
|
-
const { title = "代码审查报告", summary = [], issues = [], jiras = [], risks = [], suggestions = [] } = input;
|
|
19112
19148
|
const result = [
|
|
19113
|
-
gitlabCommitReport,
|
|
19114
19149
|
gitlabReport,
|
|
19150
|
+
gitlabCommitReport,
|
|
19115
19151
|
dingdingReport
|
|
19116
19152
|
];
|
|
19117
19153
|
if (issues.length === 0 && suggestions.length === 0) {
|
|
@@ -19120,43 +19156,40 @@ function distReports(input) {
|
|
|
19120
19156
|
dingdingReport.report = [];
|
|
19121
19157
|
return result;
|
|
19122
19158
|
}
|
|
19123
|
-
const { CI_PROJECT_NAME = "", CI_COMMIT_SHA = "", JIRA_BASE_URL, CI_MERGE_REQUEST_IID, DINGTALK_WEBHOOK } = envUsed;
|
|
19124
19159
|
const fileMap = /* @__PURE__ */ new Map();
|
|
19125
19160
|
const severityMap = /* @__PURE__ */ new Map();
|
|
19126
19161
|
const commitsSet = /* @__PURE__ */ new Set();
|
|
19127
19162
|
const commitsMap = /* @__PURE__ */ new Map();
|
|
19128
|
-
const commitComments = [];
|
|
19129
|
-
const mergeURL = pipelineMerge();
|
|
19130
19163
|
const linkBase = visitCommit();
|
|
19131
|
-
const commitsURL = commits();
|
|
19132
19164
|
function distCommitComments({ file: file$1, commitSha, severity: severity$1 = "NON", line = "", details = [], suggestions: suggestions$1 = [], codeExample = "" }) {
|
|
19133
|
-
if (commitSha
|
|
19165
|
+
if (commitSha) {
|
|
19134
19166
|
const lines = line.split(",").map((line$1) => {
|
|
19135
19167
|
const str = line$1.trim().split("-");
|
|
19136
19168
|
const num = Number.parseInt(str[str.length - 1], 10);
|
|
19137
19169
|
return Number.isNaN(num) ? void 0 : num;
|
|
19138
19170
|
}).filter((v) => v !== void 0);
|
|
19171
|
+
const attach = file$1 ? {
|
|
19172
|
+
file: file$1,
|
|
19173
|
+
line: lines.length > 0 ? lines[lines.length - 1] : void 0
|
|
19174
|
+
} : {};
|
|
19139
19175
|
commitComments.push({
|
|
19140
19176
|
sha: commitSha,
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
note: `**${severity$1}:** ${file$1}\n\n**问题:**\n\n - ${details.join("\n - ")}\n\n-------\n\n**修正建议:**\n - ${suggestions$1.join("\n - ")}${codeExample ? `\n------\n\n**代码示例:**\n${toCode(codeExample)}\n` : ""}`
|
|
19177
|
+
note: `**${severity$1}:** ${file$1}\n\n**问题:**\n\n - ${details.join("\n - ")}\n\n-------\n\n**修正建议:**\n - ${suggestions$1.join("\n - ")}${codeExample ? `\n------\n\n**代码示例:**\n${toCode(codeExample)}\n` : ""}`,
|
|
19178
|
+
...attach
|
|
19144
19179
|
});
|
|
19145
19180
|
}
|
|
19146
19181
|
}
|
|
19147
19182
|
for (const issue$1 of issues) {
|
|
19148
|
-
const { file: file$1 = "NON", commitSha = "", severity: severity$1 = "NON", commitSha: NCommitSha = "NON"
|
|
19183
|
+
const { file: file$1 = "NON", commitSha = "", severity: severity$1 = "NON", commitSha: NCommitSha = "NON" } = issue$1;
|
|
19149
19184
|
const fileList = fileMap.get(file$1) || [], issueList = severityMap.get(severity$1) || [];
|
|
19150
19185
|
fileList.push(issue$1);
|
|
19151
19186
|
fileMap.set(file$1, fileList);
|
|
19152
19187
|
issueList.push(issue$1);
|
|
19153
19188
|
severityMap.set(severity$1, issueList);
|
|
19154
|
-
if (commitSha || commitAuthor) console.error(`[调试] 问题提交信息 - 提交SHA: ${commitSha}, 作者: ${commitAuthor}, 文件: ${file$1}`);
|
|
19155
19189
|
if (commitSha) commitsSet.add(commitSha);
|
|
19156
19190
|
commitsMap.set(NCommitSha, issue$1);
|
|
19157
19191
|
distCommitComments(issue$1);
|
|
19158
19192
|
}
|
|
19159
|
-
let gitlabReportMessage = `## 📖 ${title}\n`;
|
|
19160
19193
|
if (summary && summary.length > 0) gitlabReportMessage += `> ${summary.join(" \n> ")}\n\n`;
|
|
19161
19194
|
if (risks.length > 0) gitlabReportMessage += "\n**📋 主要风险**\n - " + risks.join("\n - ");
|
|
19162
19195
|
if (fileMap.size > 0 || commitsSet.size > 0) {
|
|
@@ -19189,19 +19222,15 @@ function distReports(input) {
|
|
|
19189
19222
|
}
|
|
19190
19223
|
gitlabReportMessage += withNoFile + withFile;
|
|
19191
19224
|
}
|
|
19192
|
-
|
|
19225
|
+
const { url, type } = visitPipeline();
|
|
19193
19226
|
if (DINGTALK_WEBHOOK) {
|
|
19194
19227
|
dingdingReportMessage = `### 📖 ${title}\n\n**共计**:${issues.length}个问题\n\n${risks.length > 0 ? "**主要风险**:\n\n" + risks.join("\n\n") + "\n\n" : "\n\n"}-----\n\n**项目名**:${CI_PROJECT_NAME}`;
|
|
19195
|
-
const { url, type } = visitPipeline();
|
|
19196
19228
|
if (type) {
|
|
19197
19229
|
const { value = {} } = lastComment;
|
|
19198
19230
|
let { id: note = "" } = value || {};
|
|
19199
19231
|
note = note ? `#note_${note}` : "";
|
|
19200
|
-
|
|
19201
|
-
|
|
19202
|
-
**合并**:[${CI_MERGE_REQUEST_IID}](${url}${note})` : `
|
|
19203
|
-
|
|
19204
|
-
**提交**:[${CI_COMMIT_SHA.slice(0, 8)}](${url})`}`;
|
|
19232
|
+
const commit = note ? "&评论" : "";
|
|
19233
|
+
dingdingReportMessage += `${type === "merge_request" ? `\n\n**合并${commit}**:[${CI_MERGE_REQUEST_IID}](${url}${note})` : `\n\n**提交${commit}**:[${CI_COMMIT_SHA.slice(0, 8)}](${url}${note})`}`;
|
|
19205
19234
|
}
|
|
19206
19235
|
dingdingReportMessage += `\n\n含问题的commits:${[...commitsMap.keys()].map((v) => `[${v.slice(0, 8)}](${linkBase}/${v})`).join(", ")}\n\n-----\n`;
|
|
19207
19236
|
dingdingReportMessage += "\n#### 🐞 问题列表\n";
|
|
@@ -19216,7 +19245,6 @@ function distReports(input) {
|
|
|
19216
19245
|
break;
|
|
19217
19246
|
}
|
|
19218
19247
|
const { title: issueTitle = "NON", commitSha = "", file: file$1 = "NON", line = "NON", commitAuthor = "" } = issue$1;
|
|
19219
|
-
console.error(`[调试] 在钉钉报告中使用作者信息 - 提交SHA: ${commitSha}, 作者: ${commitAuthor}, 标题: ${issueTitle}`);
|
|
19220
19248
|
const shortSha = (commitSha || "").slice(0, 8);
|
|
19221
19249
|
const commitLink = linkBase && commitSha ? `${linkBase}/${commitSha}` : void 0;
|
|
19222
19250
|
dingdingReportMessage += `\n- ${issueTitle}\n\n - hash: ${commitLink ? `[${shortSha}](${commitLink}) ` : shortSha}\n\n - author: ${commitAuthor}\n\n - file: ${file$1}\n\n - line: ${line}`;
|
|
@@ -19225,40 +19253,23 @@ function distReports(input) {
|
|
|
19225
19253
|
}
|
|
19226
19254
|
if (jiras.length > 0) dingdingReportMessage += `\n\n\n\n#### 🔗 关联 JIRA\n ${jiras.map((v) => `[${v.key}](${JIRA_BASE_URL}/browse/${v.key}): ${v.summary}`).join("\n\n")}`;
|
|
19227
19255
|
}
|
|
19228
|
-
gitlabCommitReport.report =
|
|
19229
|
-
|
|
19230
|
-
|
|
19231
|
-
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
|
|
19243
|
-
|
|
19244
|
-
})
|
|
19245
|
-
method: "POST",
|
|
19246
|
-
headers: buildHeaders(),
|
|
19247
|
-
body: JSON.stringify({
|
|
19248
|
-
note: gitlabReportMessage,
|
|
19249
|
-
line_type: "new"
|
|
19250
|
-
})
|
|
19251
|
-
}).then((res) => res.json()).then((res) => {
|
|
19252
|
-
lastComment.value = res;
|
|
19253
|
-
})] : [() => Promise.reject(/* @__PURE__ */ new Error("⚠️ 报告内容已生成但未发布。"))];
|
|
19254
|
-
dingdingReport.report = DINGTALK_WEBHOOK ? [() => sendding(title, dingdingReportMessage).then((response) => {
|
|
19255
|
-
if (!response.ok) throw new Error(response.statusText);
|
|
19256
|
-
return response.json();
|
|
19257
|
-
}).then((res) => {
|
|
19258
|
-
if (res.errcode === 0) return;
|
|
19259
|
-
if (res.errcode === 31e4) return;
|
|
19260
|
-
throw new Error("Post comments to DingTalk with error code:" + res.errcode + ", message: " + res.errmsg);
|
|
19261
|
-
})] : [];
|
|
19256
|
+
if (commitsURL) gitlabCommitReport.report = commitComments.map(({ sha, file: file$1, line, note }) => () => {
|
|
19257
|
+
const { value = {} } = lastComment;
|
|
19258
|
+
let { id = "" } = value || {};
|
|
19259
|
+
if (id) note += `
|
|
19260
|
+
|
|
19261
|
+
[**完整评论请查看**](${url}#note_${id})`;
|
|
19262
|
+
return fetch(`${commitsURL}/${sha}/comments`, {
|
|
19263
|
+
method: "POST",
|
|
19264
|
+
headers: buildHeaders(),
|
|
19265
|
+
body: JSON.stringify({
|
|
19266
|
+
note,
|
|
19267
|
+
path: file$1,
|
|
19268
|
+
line,
|
|
19269
|
+
line_type: "new"
|
|
19270
|
+
})
|
|
19271
|
+
});
|
|
19272
|
+
});
|
|
19262
19273
|
return result;
|
|
19263
19274
|
}
|
|
19264
19275
|
async function runReport(input) {
|
package/bin/d5cli
CHANGED
|
@@ -11,7 +11,6 @@ const report = "report";
|
|
|
11
11
|
const getHash = "hash";
|
|
12
12
|
const file = "bin/copilot.js";
|
|
13
13
|
const RUNTIME_CWD = join(dirname(fileURLToPath(import.meta.url)), "../");
|
|
14
|
-
console.log("RUNTIME_CWD:", RUNTIME_CWD);
|
|
15
14
|
const serveFile = join(RUNTIME_CWD, file);
|
|
16
15
|
const envJson = buildEnv();
|
|
17
16
|
const envUsed = {
|
|
@@ -47,7 +46,7 @@ const tools = [
|
|
|
47
46
|
"--deny-tool",
|
|
48
47
|
"github-mcp-server"
|
|
49
48
|
];
|
|
50
|
-
tools.push("--model", "
|
|
49
|
+
if (platform === "linux") tools.push("--model", "gemini-3-pro-preview");
|
|
51
50
|
function toEnv(key, defaultValue) {
|
|
52
51
|
return envJson[key] || process.env[key] || defaultValue;
|
|
53
52
|
}
|
|
@@ -109,7 +108,7 @@ function installCopilot() {
|
|
|
109
108
|
//#endregion
|
|
110
109
|
//#region package.json
|
|
111
110
|
var name = "@d5render/cli";
|
|
112
|
-
var version = "0.1.
|
|
111
|
+
var version = "0.1.44";
|
|
113
112
|
|
|
114
113
|
//#endregion
|
|
115
114
|
//#region packages/gitlab/url.ts
|
|
@@ -121,15 +120,24 @@ function buildHeaders() {
|
|
|
121
120
|
else headers.set("Authorization", `Bearer ${GITLAB_TOKEN}`);
|
|
122
121
|
return headers;
|
|
123
122
|
}
|
|
124
|
-
|
|
123
|
+
function apiHost() {
|
|
124
|
+
const { CI_PROJECT_ID, CI_SERVER_URL } = envUsed;
|
|
125
|
+
if (CI_PROJECT_ID && CI_SERVER_URL) return `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}`;
|
|
126
|
+
}
|
|
127
|
+
function apiProject() {
|
|
125
128
|
const { CI_PROJECT_PATH, CI_SERVER_URL } = envUsed;
|
|
126
|
-
if (CI_PROJECT_PATH && CI_SERVER_URL) return `${CI_SERVER_URL}/${CI_PROJECT_PATH}
|
|
129
|
+
if (CI_PROJECT_PATH && CI_SERVER_URL) return `${CI_SERVER_URL}/${CI_PROJECT_PATH}`;
|
|
130
|
+
}
|
|
131
|
+
const visitCommit = () => {
|
|
132
|
+
const api = apiProject();
|
|
133
|
+
if (api) return `${api}/-/commit`;
|
|
127
134
|
};
|
|
128
135
|
function visitPipeline(mid) {
|
|
129
|
-
const
|
|
130
|
-
if (!
|
|
136
|
+
const api = apiProject();
|
|
137
|
+
if (!api) return {};
|
|
138
|
+
const { CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
|
|
131
139
|
if (CI_MERGE_REQUEST_IID) return {
|
|
132
|
-
url: `${
|
|
140
|
+
url: `${api}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
|
|
133
141
|
type: "merge_request"
|
|
134
142
|
};
|
|
135
143
|
if (CI_COMMIT_SHA) return {
|
|
@@ -139,8 +147,8 @@ function visitPipeline(mid) {
|
|
|
139
147
|
return {};
|
|
140
148
|
}
|
|
141
149
|
const commits = () => {
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
150
|
+
const api = apiHost();
|
|
151
|
+
if (api) return `${api}/repository/commits`;
|
|
144
152
|
};
|
|
145
153
|
|
|
146
154
|
//#endregion
|
|
@@ -170,8 +178,6 @@ async function sendding(title, text) {
|
|
|
170
178
|
//#region copilot/bin/utils.ts
|
|
171
179
|
const NAME = name.replaceAll("/", "_");
|
|
172
180
|
const VERSION = version;
|
|
173
|
-
const HOME = env.USERPROFILE ?? env.HOME ?? env.HOMEPATH;
|
|
174
|
-
if (!HOME) throw new Error("cannot find `USERPROFILE` directory");
|
|
175
181
|
const TEMP = env.CI_PROJECT_DIR + "." + NAME;
|
|
176
182
|
const dingding = async (...args) => {
|
|
177
183
|
try {
|
|
@@ -183,32 +189,38 @@ const dingding = async (...args) => {
|
|
|
183
189
|
};
|
|
184
190
|
async function deploy() {
|
|
185
191
|
if (!env.CI) return;
|
|
186
|
-
const
|
|
192
|
+
const HOME = env.USERPROFILE ?? env.HOME ?? env.HOMEPATH;
|
|
193
|
+
if (!HOME) throw new Error("cannot find `USERPROFILE` directory");
|
|
194
|
+
const config = join(HOME, ".copilot/config.json"), dir = join(HOME, ".copilot/skills/code-review");
|
|
187
195
|
console.log("deploy...");
|
|
188
196
|
if (existsSync(config)) {
|
|
189
197
|
rmSync(config);
|
|
190
198
|
console.log("removed config cache.");
|
|
191
199
|
}
|
|
192
200
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
193
|
-
const versiono = readFileSync(join(RUNTIME_CWD, ".skills/review/version"), "utf8");
|
|
201
|
+
const versiono = readFileSync(join(RUNTIME_CWD, ".skills/code-review/version"), "utf8");
|
|
194
202
|
const changelog = readFileSync(join(RUNTIME_CWD, "CHANGELOG.md"), "utf8");
|
|
195
|
-
const cachepath = join(TEMP, "CHANGELOG");
|
|
203
|
+
const cachepath = join(TEMP, "CHANGELOG-" + env.CI_RUNNER_ID || "0");
|
|
196
204
|
if (changelog !== (existsSync(cachepath) ? readFileSync(cachepath, "utf8") : "")) {
|
|
197
205
|
if (!existsSync(TEMP)) mkdirSync(TEMP, { recursive: true });
|
|
198
206
|
writeFileSync(cachepath, changelog, "utf8");
|
|
199
207
|
console.log("updated CHANGELOG cache.");
|
|
200
|
-
await dingding("NOTICE", `
|
|
208
|
+
await dingding("NOTICE", `code-review/SKILL.md 更新到${versiono}\n\n请参考[线上文档内容](https://www.npmjs.com/package/@d5render/cli?activeTab=readme)`);
|
|
201
209
|
}
|
|
202
210
|
const versionnPath = join(dir, "version");
|
|
203
211
|
if ((existsSync(versionnPath) ? readFileSync(versionnPath, "utf8") : "") === versiono) return;
|
|
204
|
-
|
|
212
|
+
if (existsSync(dir)) rmSync(dir, {
|
|
213
|
+
recursive: true,
|
|
214
|
+
force: true
|
|
215
|
+
});
|
|
216
|
+
const skillRoot = join(RUNTIME_CWD, ".skills/code-review");
|
|
205
217
|
readdirSync(skillRoot).forEach((skill) => copyFileSync(join(skillRoot, skill), join(dir, skill)));
|
|
206
218
|
const instructionsRoot = join(RUNTIME_CWD, ".github/instructions");
|
|
207
219
|
readdirSync(instructionsRoot).forEach((instruction) => copyFileSync(join(instructionsRoot, instruction), join(dir, instruction)));
|
|
208
220
|
console.log("to new skill.");
|
|
209
221
|
}
|
|
210
222
|
async function need() {
|
|
211
|
-
if (!env.CI) return;
|
|
223
|
+
if (!env.CI) return true;
|
|
212
224
|
const { CI_MERGE_REQUEST_IID, CI_COMMIT_SHA } = env;
|
|
213
225
|
const file$1 = join(TEMP, "CODEREVIEW");
|
|
214
226
|
if (CI_MERGE_REQUEST_IID) {
|
|
@@ -278,29 +290,18 @@ Otherwise, use chinese as default language to call the mcp tool '${name$1}-${rep
|
|
|
278
290
|
],
|
|
279
291
|
...platform === "win32" && { windowsHide: true }
|
|
280
292
|
});
|
|
281
|
-
copilot.stdout.on("data", (chunk) => console.log(
|
|
282
|
-
copilot.stderr.on("data", (chunk) => console.error(
|
|
293
|
+
copilot.stdout.on("data", (chunk) => console.log(String(chunk)));
|
|
294
|
+
copilot.stderr.on("data", (chunk) => console.error(String(chunk)));
|
|
283
295
|
copilot.on("close", (code) => {
|
|
284
296
|
if (!code) dingding("CRITICAL", "CI ERROR: 代码审查任务失败,请自行检查日志");
|
|
285
297
|
exit(code);
|
|
286
298
|
});
|
|
287
299
|
}
|
|
288
|
-
/**
|
|
289
|
-
* 根据操作系统和架构获取对应的 copilot 包名
|
|
290
|
-
*/
|
|
291
|
-
function getCopilotPackageName() {
|
|
292
|
-
return "@github/copilot";
|
|
293
|
-
}
|
|
294
300
|
function findCopilopt() {
|
|
295
|
-
const platformPackage = getCopilotPackageName();
|
|
296
301
|
let copilot = "";
|
|
297
302
|
try {
|
|
298
|
-
copilot = execSync(
|
|
299
|
-
} catch {
|
|
300
|
-
try {
|
|
301
|
-
copilot = execSync("npm list @github/copilot -g -p").toString().trim();
|
|
302
|
-
} catch {}
|
|
303
|
-
}
|
|
303
|
+
copilot = execSync("npm list @github/copilot -g -p").toString().trim();
|
|
304
|
+
} catch {}
|
|
304
305
|
if (!copilot) {
|
|
305
306
|
const first = platform === "win32" ? win : linux;
|
|
306
307
|
const second = platform === "win32" ? linux : win;
|
|
@@ -311,7 +312,7 @@ function findCopilopt() {
|
|
|
311
312
|
const pkg = join(copilot, "package.json");
|
|
312
313
|
if (!existsSync(pkg)) throw new Error("安装的包找不到正确版本 " + pkg);
|
|
313
314
|
const copilotPackage = JSON.parse(readFileSync(pkg, "utf8"));
|
|
314
|
-
const binPath = typeof copilotPackage.bin === "string" ? copilotPackage.bin : copilotPackage.bin?.copilot || copilotPackage.bin?.[
|
|
315
|
+
const binPath = typeof copilotPackage.bin === "string" ? copilotPackage.bin : copilotPackage.bin?.copilot || copilotPackage.bin?.["@github/copilot"];
|
|
315
316
|
if (!binPath) throw new Error("non copilot executable found");
|
|
316
317
|
const copilotVersion = copilotPackage.version || "unknown";
|
|
317
318
|
const copilotPath = join(copilot, binPath);
|
|
@@ -328,9 +329,7 @@ function win() {
|
|
|
328
329
|
const pathSeparator = platform === "win32" ? ";" : ":";
|
|
329
330
|
const npm = pathEnv.split(pathSeparator).find((p) => p.includes("npm"));
|
|
330
331
|
if (!npm) return "";
|
|
331
|
-
const
|
|
332
|
-
if (existsSync(join(packagePath, "package.json"))) return packagePath;
|
|
333
|
-
const fallbackPath = join(npm, "node_modules", "@github", "copilot");
|
|
332
|
+
const fallbackPath = join(npm, "node_modules", "@github/copilot");
|
|
334
333
|
if (existsSync(join(fallbackPath, "package.json"))) return fallbackPath;
|
|
335
334
|
return "";
|
|
336
335
|
}
|
|
@@ -343,10 +342,7 @@ function linux() {
|
|
|
343
342
|
if (npm) cached = npm;
|
|
344
343
|
}
|
|
345
344
|
if (!cached) return "";
|
|
346
|
-
const
|
|
347
|
-
const packagePath = join(cached, "..", "lib", "node_modules", ...platformPackage.split("/"));
|
|
348
|
-
if (existsSync(join(packagePath, "package.json"))) return packagePath;
|
|
349
|
-
const fallbackPath = join(cached, "..", "lib", "node_modules", "@github", "copilot");
|
|
345
|
+
const fallbackPath = join(cached, "..", "lib", "node_modules", "@github/copilot");
|
|
350
346
|
if (existsSync(join(fallbackPath, "package.json"))) return fallbackPath;
|
|
351
347
|
return "";
|
|
352
348
|
}
|
package/package.json
CHANGED
|
@@ -4,22 +4,24 @@
|
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "jasirou",
|
|
6
6
|
"main": "./bin/d5cli",
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.44",
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
10
10
|
"@types/node": "^25.0.3",
|
|
11
11
|
"@types/vscode": "^1.107.0",
|
|
12
12
|
"@vscode/vsce": "^3.7.1",
|
|
13
|
+
"json5": "^2.2.3",
|
|
13
14
|
"oxfmt": "^0.21.0",
|
|
14
15
|
"oxlint": "^1.37.0",
|
|
15
16
|
"rolldown": "1.0.0-beta.58",
|
|
17
|
+
"vitest": "^4.0.17",
|
|
16
18
|
"zod": "^4.3.5"
|
|
17
19
|
},
|
|
18
20
|
"files": [
|
|
19
|
-
".github/instructions/review.instructions.md",
|
|
21
|
+
".github/instructions/code-review.instructions.md",
|
|
20
22
|
".github/instructions/severity.instructions.md",
|
|
21
23
|
".skills/devops",
|
|
22
|
-
".skills/review",
|
|
24
|
+
".skills/code-review",
|
|
23
25
|
"bin/copilot.js",
|
|
24
26
|
"bin/d5cli",
|
|
25
27
|
"CHANGELOG.md"
|
|
File without changes
|
|
File without changes
|