@d5render/cli 0.1.43 → 0.1.49
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 +105 -97
- package/bin/d5cli +44 -52
- package/package.json +5 -3
- package/.skills/review/version +0 -1
- /package/.github/instructions/{review.instructions.md → code-review.instructions.md} +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
|
@@ -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,10 +18844,7 @@ const tools = [
|
|
|
18845
18844
|
"--deny-tool",
|
|
18846
18845
|
"github-mcp-server"
|
|
18847
18846
|
];
|
|
18848
|
-
|
|
18849
|
-
const model = platform === "linux" ? "gemini-3-pro-preview" : "claude-sonnet-4.5";
|
|
18850
|
-
tools.push("--model", model);
|
|
18851
|
-
}
|
|
18847
|
+
if (platform === "linux") tools.push("--model", "gemini-3-pro-preview");
|
|
18852
18848
|
function toEnv(key, defaultValue) {
|
|
18853
18849
|
return envJson[key] || process.env[key] || defaultValue;
|
|
18854
18850
|
}
|
|
@@ -18869,19 +18865,29 @@ function buildHeaders() {
|
|
|
18869
18865
|
else headers.set("Authorization", `Bearer ${GITLAB_TOKEN}`);
|
|
18870
18866
|
return headers;
|
|
18871
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
|
+
}
|
|
18872
18876
|
const pipelineMerge = () => {
|
|
18873
|
-
const
|
|
18874
|
-
|
|
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}`;
|
|
18875
18880
|
};
|
|
18876
18881
|
const visitCommit = () => {
|
|
18877
|
-
const
|
|
18878
|
-
if (
|
|
18882
|
+
const api = apiProject();
|
|
18883
|
+
if (api) return `${api}/-/commit`;
|
|
18879
18884
|
};
|
|
18880
18885
|
function visitPipeline(mid) {
|
|
18881
|
-
const
|
|
18882
|
-
if (!
|
|
18886
|
+
const api = apiProject();
|
|
18887
|
+
if (!api) return {};
|
|
18888
|
+
const { CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
|
|
18883
18889
|
if (CI_MERGE_REQUEST_IID) return {
|
|
18884
|
-
url: `${
|
|
18890
|
+
url: `${api}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
|
|
18885
18891
|
type: "merge_request"
|
|
18886
18892
|
};
|
|
18887
18893
|
if (CI_COMMIT_SHA) return {
|
|
@@ -18891,45 +18897,29 @@ function visitPipeline(mid) {
|
|
|
18891
18897
|
return {};
|
|
18892
18898
|
}
|
|
18893
18899
|
const commits = () => {
|
|
18894
|
-
const
|
|
18895
|
-
if (
|
|
18900
|
+
const api = apiHost();
|
|
18901
|
+
if (api) return `${api}/repository/commits`;
|
|
18896
18902
|
};
|
|
18897
18903
|
|
|
18898
18904
|
//#endregion
|
|
18899
18905
|
//#region packages/gitlab/commit.ts
|
|
18900
18906
|
const getCommits = () => {
|
|
18901
|
-
const { CI_COMMIT_SHA, CI_COMMIT_BEFORE_SHA } = envUsed;
|
|
18902
18907
|
const url = pipelineMerge();
|
|
18903
18908
|
if (url) return fetch(`${url}/commits`, { headers: buildHeaders() }).then((res) => {
|
|
18904
|
-
if (!res.ok) throw new Error("");
|
|
18909
|
+
if (!res.ok) throw new Error("请求GitLab API失败" + res.statusText);
|
|
18905
18910
|
return res.json();
|
|
18906
|
-
}).then((commits$1) => {
|
|
18907
|
-
|
|
18908
|
-
commits
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
短ID: commit.short_id,
|
|
18912
|
-
标题: commit.title,
|
|
18913
|
-
消息: commit.message?.substring(0, 50),
|
|
18914
|
-
作者姓名: commit.author_name,
|
|
18915
|
-
作者邮箱: commit.author_email,
|
|
18916
|
-
提交日期: commit.authored_date,
|
|
18917
|
-
提交者姓名: commit.committer_name,
|
|
18918
|
-
提交者邮箱: commit.committer_email
|
|
18919
|
-
});
|
|
18920
|
-
});
|
|
18921
|
-
return { content: [{
|
|
18922
|
-
type: "text",
|
|
18923
|
-
description: "commits from `merge pipeline`",
|
|
18924
|
-
text: `${commits$1.map((commit) => commit.id).join(",")}, the above review is from \`merge pipeline\` commits.`
|
|
18925
|
-
}] };
|
|
18926
|
-
}).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) => ({
|
|
18927
18916
|
content: [{
|
|
18928
18917
|
type: "text",
|
|
18929
|
-
text: "error
|
|
18918
|
+
text: "error " + (error.message || "请求GitLab API失败,请检查网络连接或访问权限")
|
|
18930
18919
|
}],
|
|
18931
18920
|
isError: true
|
|
18932
18921
|
}));
|
|
18922
|
+
const { CI_COMMIT_SHA, CI_COMMIT_BEFORE_SHA } = envUsed;
|
|
18933
18923
|
if (CI_COMMIT_SHA && CI_COMMIT_BEFORE_SHA) return { content: [{
|
|
18934
18924
|
type: "text",
|
|
18935
18925
|
description: "commit from push pipeline",
|
|
@@ -18985,7 +18975,8 @@ function commonHeaders() {
|
|
|
18985
18975
|
const getIssue = (request) => {
|
|
18986
18976
|
const { key } = request;
|
|
18987
18977
|
const { JIRA_BASE_URL } = envUsed;
|
|
18988
|
-
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;
|
|
18989
18980
|
const { comment = {}, description = "", summary = "", priority = {} } = fields;
|
|
18990
18981
|
const { comments = [] } = comment;
|
|
18991
18982
|
return { content: [{
|
|
@@ -19099,22 +19090,64 @@ const schemaGitlabPipeline = "gitlab_pipeline";
|
|
|
19099
19090
|
const schemaGitlabCommit = "gitlab_commit";
|
|
19100
19091
|
const schemaDingdingTalk = "dingding_talk";
|
|
19101
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 = "";
|
|
19102
19100
|
const gitlabReport = {
|
|
19103
19101
|
title: schemaGitlabPipeline,
|
|
19104
|
-
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
|
+
}]
|
|
19105
19129
|
};
|
|
19106
19130
|
const gitlabCommitReport = {
|
|
19107
19131
|
title: schemaGitlabCommit,
|
|
19108
|
-
report: [() => Promise.resolve(
|
|
19132
|
+
report: [() => Promise.resolve()]
|
|
19109
19133
|
};
|
|
19110
19134
|
const dingdingReport = {
|
|
19111
19135
|
title: schemaDingdingTalk,
|
|
19112
|
-
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
|
+
}]
|
|
19113
19147
|
};
|
|
19114
|
-
const { title = "代码审查报告", summary = [], issues = [], jiras = [], risks = [], suggestions = [] } = input;
|
|
19115
19148
|
const result = [
|
|
19116
|
-
gitlabCommitReport,
|
|
19117
19149
|
gitlabReport,
|
|
19150
|
+
gitlabCommitReport,
|
|
19118
19151
|
dingdingReport
|
|
19119
19152
|
];
|
|
19120
19153
|
if (issues.length === 0 && suggestions.length === 0) {
|
|
@@ -19123,43 +19156,40 @@ function distReports(input) {
|
|
|
19123
19156
|
dingdingReport.report = [];
|
|
19124
19157
|
return result;
|
|
19125
19158
|
}
|
|
19126
|
-
const { CI_PROJECT_NAME = "", CI_COMMIT_SHA = "", JIRA_BASE_URL, CI_MERGE_REQUEST_IID, DINGTALK_WEBHOOK } = envUsed;
|
|
19127
19159
|
const fileMap = /* @__PURE__ */ new Map();
|
|
19128
19160
|
const severityMap = /* @__PURE__ */ new Map();
|
|
19129
19161
|
const commitsSet = /* @__PURE__ */ new Set();
|
|
19130
19162
|
const commitsMap = /* @__PURE__ */ new Map();
|
|
19131
|
-
const commitComments = [];
|
|
19132
|
-
const mergeURL = pipelineMerge();
|
|
19133
19163
|
const linkBase = visitCommit();
|
|
19134
|
-
const commitsURL = commits();
|
|
19135
19164
|
function distCommitComments({ file: file$1, commitSha, severity: severity$1 = "NON", line = "", details = [], suggestions: suggestions$1 = [], codeExample = "" }) {
|
|
19136
|
-
if (commitSha
|
|
19165
|
+
if (commitSha) {
|
|
19137
19166
|
const lines = line.split(",").map((line$1) => {
|
|
19138
19167
|
const str = line$1.trim().split("-");
|
|
19139
19168
|
const num = Number.parseInt(str[str.length - 1], 10);
|
|
19140
19169
|
return Number.isNaN(num) ? void 0 : num;
|
|
19141
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
|
+
} : {};
|
|
19142
19175
|
commitComments.push({
|
|
19143
19176
|
sha: commitSha,
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
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
|
|
19147
19179
|
});
|
|
19148
19180
|
}
|
|
19149
19181
|
}
|
|
19150
19182
|
for (const issue$1 of issues) {
|
|
19151
|
-
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;
|
|
19152
19184
|
const fileList = fileMap.get(file$1) || [], issueList = severityMap.get(severity$1) || [];
|
|
19153
19185
|
fileList.push(issue$1);
|
|
19154
19186
|
fileMap.set(file$1, fileList);
|
|
19155
19187
|
issueList.push(issue$1);
|
|
19156
19188
|
severityMap.set(severity$1, issueList);
|
|
19157
|
-
if (commitSha || commitAuthor) console.error(`[调试] 问题提交信息 - 提交SHA: ${commitSha}, 作者: ${commitAuthor}, 文件: ${file$1}`);
|
|
19158
19189
|
if (commitSha) commitsSet.add(commitSha);
|
|
19159
19190
|
commitsMap.set(NCommitSha, issue$1);
|
|
19160
19191
|
distCommitComments(issue$1);
|
|
19161
19192
|
}
|
|
19162
|
-
let gitlabReportMessage = `## 📖 ${title}\n`;
|
|
19163
19193
|
if (summary && summary.length > 0) gitlabReportMessage += `> ${summary.join(" \n> ")}\n\n`;
|
|
19164
19194
|
if (risks.length > 0) gitlabReportMessage += "\n**📋 主要风险**\n - " + risks.join("\n - ");
|
|
19165
19195
|
if (fileMap.size > 0 || commitsSet.size > 0) {
|
|
@@ -19192,19 +19222,15 @@ function distReports(input) {
|
|
|
19192
19222
|
}
|
|
19193
19223
|
gitlabReportMessage += withNoFile + withFile;
|
|
19194
19224
|
}
|
|
19195
|
-
|
|
19225
|
+
const { url, type } = visitPipeline();
|
|
19196
19226
|
if (DINGTALK_WEBHOOK) {
|
|
19197
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}`;
|
|
19198
|
-
const { url, type } = visitPipeline();
|
|
19199
19228
|
if (type) {
|
|
19200
19229
|
const { value = {} } = lastComment;
|
|
19201
19230
|
let { id: note = "" } = value || {};
|
|
19202
19231
|
note = note ? `#note_${note}` : "";
|
|
19203
|
-
|
|
19204
|
-
|
|
19205
|
-
**合并**:[${CI_MERGE_REQUEST_IID}](${url}${note})` : `
|
|
19206
|
-
|
|
19207
|
-
**提交**:[${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})`}`;
|
|
19208
19234
|
}
|
|
19209
19235
|
dingdingReportMessage += `\n\n含问题的commits:${[...commitsMap.keys()].map((v) => `[${v.slice(0, 8)}](${linkBase}/${v})`).join(", ")}\n\n-----\n`;
|
|
19210
19236
|
dingdingReportMessage += "\n#### 🐞 问题列表\n";
|
|
@@ -19219,7 +19245,6 @@ function distReports(input) {
|
|
|
19219
19245
|
break;
|
|
19220
19246
|
}
|
|
19221
19247
|
const { title: issueTitle = "NON", commitSha = "", file: file$1 = "NON", line = "NON", commitAuthor = "" } = issue$1;
|
|
19222
|
-
console.error(`[调试] 在钉钉报告中使用作者信息 - 提交SHA: ${commitSha}, 作者: ${commitAuthor}, 标题: ${issueTitle}`);
|
|
19223
19248
|
const shortSha = (commitSha || "").slice(0, 8);
|
|
19224
19249
|
const commitLink = linkBase && commitSha ? `${linkBase}/${commitSha}` : void 0;
|
|
19225
19250
|
dingdingReportMessage += `\n- ${issueTitle}\n\n - hash: ${commitLink ? `[${shortSha}](${commitLink}) ` : shortSha}\n\n - author: ${commitAuthor}\n\n - file: ${file$1}\n\n - line: ${line}`;
|
|
@@ -19228,40 +19253,23 @@ function distReports(input) {
|
|
|
19228
19253
|
}
|
|
19229
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")}`;
|
|
19230
19255
|
}
|
|
19231
|
-
gitlabCommitReport.report =
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
|
|
19243
|
-
|
|
19244
|
-
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
})
|
|
19248
|
-
method: "POST",
|
|
19249
|
-
headers: buildHeaders(),
|
|
19250
|
-
body: JSON.stringify({
|
|
19251
|
-
note: gitlabReportMessage,
|
|
19252
|
-
line_type: "new"
|
|
19253
|
-
})
|
|
19254
|
-
}).then((res) => res.json()).then((res) => {
|
|
19255
|
-
lastComment.value = res;
|
|
19256
|
-
})] : [() => Promise.reject(/* @__PURE__ */ new Error("⚠️ 报告内容已生成但未发布。"))];
|
|
19257
|
-
dingdingReport.report = DINGTALK_WEBHOOK ? [() => sendding(title, dingdingReportMessage).then((response) => {
|
|
19258
|
-
if (!response.ok) throw new Error(response.statusText);
|
|
19259
|
-
return response.json();
|
|
19260
|
-
}).then((res) => {
|
|
19261
|
-
if (res.errcode === 0) return;
|
|
19262
|
-
if (res.errcode === 31e4) return;
|
|
19263
|
-
throw new Error("Post comments to DingTalk with error code:" + res.errcode + ", message: " + res.errmsg);
|
|
19264
|
-
})] : [];
|
|
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
|
+
});
|
|
19265
19273
|
return result;
|
|
19266
19274
|
}
|
|
19267
19275
|
async function runReport(input) {
|
package/bin/d5cli
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { execSync, spawn } from "node:child_process";
|
|
3
3
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { argv, env,
|
|
5
|
+
import { argv, env, platform } from "node:process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
//#region copilot/server/config.ts
|
|
@@ -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,10 +46,7 @@ const tools = [
|
|
|
47
46
|
"--deny-tool",
|
|
48
47
|
"github-mcp-server"
|
|
49
48
|
];
|
|
50
|
-
|
|
51
|
-
const model = platform === "linux" ? "gemini-3-pro-preview" : "claude-sonnet-4.5";
|
|
52
|
-
tools.push("--model", model);
|
|
53
|
-
}
|
|
49
|
+
if (platform === "linux") tools.push("--model", "gemini-3-pro-preview");
|
|
54
50
|
function toEnv(key, defaultValue) {
|
|
55
51
|
return envJson[key] || process.env[key] || defaultValue;
|
|
56
52
|
}
|
|
@@ -112,7 +108,7 @@ function installCopilot() {
|
|
|
112
108
|
//#endregion
|
|
113
109
|
//#region package.json
|
|
114
110
|
var name = "@d5render/cli";
|
|
115
|
-
var version = "0.1.
|
|
111
|
+
var version = "0.1.49";
|
|
116
112
|
|
|
117
113
|
//#endregion
|
|
118
114
|
//#region packages/gitlab/url.ts
|
|
@@ -124,15 +120,24 @@ function buildHeaders() {
|
|
|
124
120
|
else headers.set("Authorization", `Bearer ${GITLAB_TOKEN}`);
|
|
125
121
|
return headers;
|
|
126
122
|
}
|
|
127
|
-
|
|
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() {
|
|
128
128
|
const { CI_PROJECT_PATH, CI_SERVER_URL } = envUsed;
|
|
129
|
-
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`;
|
|
130
134
|
};
|
|
131
135
|
function visitPipeline(mid) {
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
136
|
+
const api = apiProject();
|
|
137
|
+
if (!api) return {};
|
|
138
|
+
const { CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
|
|
134
139
|
if (CI_MERGE_REQUEST_IID) return {
|
|
135
|
-
url: `${
|
|
140
|
+
url: `${api}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
|
|
136
141
|
type: "merge_request"
|
|
137
142
|
};
|
|
138
143
|
if (CI_COMMIT_SHA) return {
|
|
@@ -142,8 +147,8 @@ function visitPipeline(mid) {
|
|
|
142
147
|
return {};
|
|
143
148
|
}
|
|
144
149
|
const commits = () => {
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
150
|
+
const api = apiHost();
|
|
151
|
+
if (api) return `${api}/repository/commits`;
|
|
147
152
|
};
|
|
148
153
|
|
|
149
154
|
//#endregion
|
|
@@ -173,8 +178,6 @@ async function sendding(title, text) {
|
|
|
173
178
|
//#region copilot/bin/utils.ts
|
|
174
179
|
const NAME = name.replaceAll("/", "_");
|
|
175
180
|
const VERSION = version;
|
|
176
|
-
const HOME = env.USERPROFILE ?? env.HOME ?? env.HOMEPATH;
|
|
177
|
-
if (!HOME) throw new Error("cannot find `USERPROFILE` directory");
|
|
178
181
|
const TEMP = env.CI_PROJECT_DIR + "." + NAME;
|
|
179
182
|
const dingding = async (...args) => {
|
|
180
183
|
try {
|
|
@@ -186,25 +189,30 @@ const dingding = async (...args) => {
|
|
|
186
189
|
};
|
|
187
190
|
async function deploy() {
|
|
188
191
|
if (!env.CI) return;
|
|
189
|
-
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");
|
|
190
195
|
console.log("deploy...");
|
|
191
196
|
if (existsSync(config)) {
|
|
192
197
|
rmSync(config);
|
|
193
198
|
console.log("removed config cache.");
|
|
194
199
|
}
|
|
195
200
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
196
|
-
const versiono = readFileSync(join(RUNTIME_CWD, ".skills/review/version"), "utf8");
|
|
197
201
|
const changelog = readFileSync(join(RUNTIME_CWD, "CHANGELOG.md"), "utf8");
|
|
198
|
-
const cachepath = join(TEMP, "CHANGELOG");
|
|
202
|
+
const cachepath = join(TEMP, "CHANGELOG-" + env.CI_RUNNER_ID || "0");
|
|
199
203
|
if (changelog !== (existsSync(cachepath) ? readFileSync(cachepath, "utf8") : "")) {
|
|
200
204
|
if (!existsSync(TEMP)) mkdirSync(TEMP, { recursive: true });
|
|
201
205
|
writeFileSync(cachepath, changelog, "utf8");
|
|
202
206
|
console.log("updated CHANGELOG cache.");
|
|
203
|
-
await dingding("NOTICE", `
|
|
207
|
+
await dingding("NOTICE", `code-review/SKILL.md 更新到${VERSION}\n\n请参考[线上文档内容](https://www.npmjs.com/package/@d5render/cli?activeTab=readme)`);
|
|
204
208
|
}
|
|
205
209
|
const versionnPath = join(dir, "version");
|
|
206
|
-
if ((existsSync(versionnPath) ? readFileSync(versionnPath, "utf8") : "") ===
|
|
207
|
-
|
|
210
|
+
if ((existsSync(versionnPath) ? readFileSync(versionnPath, "utf8") : "") === VERSION) return;
|
|
211
|
+
if (existsSync(dir)) rmSync(dir, {
|
|
212
|
+
recursive: true,
|
|
213
|
+
force: true
|
|
214
|
+
});
|
|
215
|
+
const skillRoot = join(RUNTIME_CWD, ".skills/code-review");
|
|
208
216
|
readdirSync(skillRoot).forEach((skill) => copyFileSync(join(skillRoot, skill), join(dir, skill)));
|
|
209
217
|
const instructionsRoot = join(RUNTIME_CWD, ".github/instructions");
|
|
210
218
|
readdirSync(instructionsRoot).forEach((instruction) => copyFileSync(join(instructionsRoot, instruction), join(dir, instruction)));
|
|
@@ -250,12 +258,10 @@ async function need() {
|
|
|
250
258
|
|
|
251
259
|
//#endregion
|
|
252
260
|
//#region copilot/bin/index.ts
|
|
253
|
-
|
|
254
|
-
codereview();
|
|
255
|
-
} catch (error) {
|
|
261
|
+
codereview().catch((error) => {
|
|
256
262
|
dingding("CRITICAL", "CI ERROR: 未知错误,请自行检查日志");
|
|
257
263
|
throw error;
|
|
258
|
-
}
|
|
264
|
+
});
|
|
259
265
|
async function codereview() {
|
|
260
266
|
await deploy();
|
|
261
267
|
if (!await need()) {
|
|
@@ -281,29 +287,20 @@ Otherwise, use chinese as default language to call the mcp tool '${name$1}-${rep
|
|
|
281
287
|
],
|
|
282
288
|
...platform === "win32" && { windowsHide: true }
|
|
283
289
|
});
|
|
284
|
-
copilot.stdout.on("data", (chunk) => console.log(
|
|
285
|
-
copilot.stderr.on("data", (chunk) => console.error(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
290
|
+
copilot.stdout.on("data", (chunk) => console.log(String(chunk)));
|
|
291
|
+
copilot.stderr.on("data", (chunk) => console.error(String(chunk)));
|
|
292
|
+
return new Promise((res, rej) => {
|
|
293
|
+
copilot.on("close", (code) => {
|
|
294
|
+
if (!code) rej(/* @__PURE__ */ new Error("CI ERROR: 代码审查任务失败,请自行检查日志"));
|
|
295
|
+
else res();
|
|
296
|
+
});
|
|
289
297
|
});
|
|
290
298
|
}
|
|
291
|
-
/**
|
|
292
|
-
* 根据操作系统和架构获取对应的 copilot 包名
|
|
293
|
-
*/
|
|
294
|
-
function getCopilotPackageName() {
|
|
295
|
-
return "@github/copilot";
|
|
296
|
-
}
|
|
297
299
|
function findCopilopt() {
|
|
298
|
-
const platformPackage = getCopilotPackageName();
|
|
299
300
|
let copilot = "";
|
|
300
301
|
try {
|
|
301
|
-
copilot = execSync(
|
|
302
|
-
} catch {
|
|
303
|
-
try {
|
|
304
|
-
copilot = execSync("npm list @github/copilot -g -p").toString().trim();
|
|
305
|
-
} catch {}
|
|
306
|
-
}
|
|
302
|
+
copilot = execSync("npm list @github/copilot -g -p").toString().trim();
|
|
303
|
+
} catch {}
|
|
307
304
|
if (!copilot) {
|
|
308
305
|
const first = platform === "win32" ? win : linux;
|
|
309
306
|
const second = platform === "win32" ? linux : win;
|
|
@@ -314,7 +311,7 @@ function findCopilopt() {
|
|
|
314
311
|
const pkg = join(copilot, "package.json");
|
|
315
312
|
if (!existsSync(pkg)) throw new Error("安装的包找不到正确版本 " + pkg);
|
|
316
313
|
const copilotPackage = JSON.parse(readFileSync(pkg, "utf8"));
|
|
317
|
-
const binPath = typeof copilotPackage.bin === "string" ? copilotPackage.bin : copilotPackage.bin?.copilot || copilotPackage.bin?.[
|
|
314
|
+
const binPath = typeof copilotPackage.bin === "string" ? copilotPackage.bin : copilotPackage.bin?.copilot || copilotPackage.bin?.["@github/copilot"];
|
|
318
315
|
if (!binPath) throw new Error("non copilot executable found");
|
|
319
316
|
const copilotVersion = copilotPackage.version || "unknown";
|
|
320
317
|
const copilotPath = join(copilot, binPath);
|
|
@@ -331,9 +328,7 @@ function win() {
|
|
|
331
328
|
const pathSeparator = platform === "win32" ? ";" : ":";
|
|
332
329
|
const npm = pathEnv.split(pathSeparator).find((p) => p.includes("npm"));
|
|
333
330
|
if (!npm) return "";
|
|
334
|
-
const
|
|
335
|
-
if (existsSync(join(packagePath, "package.json"))) return packagePath;
|
|
336
|
-
const fallbackPath = join(npm, "node_modules", "@github", "copilot");
|
|
331
|
+
const fallbackPath = join(npm, "node_modules", "@github/copilot");
|
|
337
332
|
if (existsSync(join(fallbackPath, "package.json"))) return fallbackPath;
|
|
338
333
|
return "";
|
|
339
334
|
}
|
|
@@ -346,10 +341,7 @@ function linux() {
|
|
|
346
341
|
if (npm) cached = npm;
|
|
347
342
|
}
|
|
348
343
|
if (!cached) return "";
|
|
349
|
-
const
|
|
350
|
-
const packagePath = join(cached, "..", "lib", "node_modules", ...platformPackage.split("/"));
|
|
351
|
-
if (existsSync(join(packagePath, "package.json"))) return packagePath;
|
|
352
|
-
const fallbackPath = join(cached, "..", "lib", "node_modules", "@github", "copilot");
|
|
344
|
+
const fallbackPath = join(cached, "..", "lib", "node_modules", "@github/copilot");
|
|
353
345
|
if (existsSync(join(fallbackPath, "package.json"))) return fallbackPath;
|
|
354
346
|
return "";
|
|
355
347
|
}
|
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.49",
|
|
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"
|
package/.skills/review/version
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.0.1
|
|
File without changes
|