@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.
@@ -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.
@@ -10,7 +10,7 @@ CI MCP 的入口是 [server.ts](.\copilot\server\index.ts) 会经历一轮打包
10
10
 
11
11
  开发的一些注意点:
12
12
 
13
- 1. [copilot.config.js](.\copilot\server\config.ts) 属于公共配置,有需要可以放这里
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
@@ -1,7 +1,10 @@
1
- 2026-1-7
1
+ ### 2026-1-16
2
+ 补充安全相关检查
3
+
4
+ ### 2026-1-7
2
5
  代码评审 skills 更新
3
6
 
4
7
  使用内置 skills
5
8
 
6
- 2025-12-31
9
+ ### 2025-12-31
7
10
  基础评审能力上云
package/README.md CHANGED
@@ -1,3 +1,3 @@
1
1
  for devops, please refer [.skills/devops](.skills/devops)
2
2
 
3
- for codereview, please refer[.skills/review/SKILL.md](.skills/review/SKILL.md)
3
+ for codereview, please refer[.skills/code-review/SKILL.md](.skills/code-review/SKILL.md)
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 { CI_MERGE_REQUEST_IID, CI_PROJECT_ID, CI_SERVER_URL } = envUsed;
18874
- if (CI_MERGE_REQUEST_IID && CI_PROJECT_ID && CI_SERVER_URL) return `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}`;
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 { CI_PROJECT_PATH, CI_SERVER_URL } = envUsed;
18878
- if (CI_PROJECT_PATH && CI_SERVER_URL) return `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/commit`;
18882
+ const api = apiProject();
18883
+ if (api) return `${api}/-/commit`;
18879
18884
  };
18880
18885
  function visitPipeline(mid) {
18881
- const { CI_SERVER_URL, CI_PROJECT_PATH, CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
18882
- if (!CI_SERVER_URL || !CI_PROJECT_PATH) return {};
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: `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
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 { CI_PROJECT_ID, CI_SERVER_URL } = envUsed;
18895
- if (CI_PROJECT_ID && CI_SERVER_URL) return `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/repository/commits`;
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
- console.log(`[调试] 从 GitLab API 获取了 ${commits$1.length} 个提交记录`);
18908
- commits$1.forEach((commit, index) => {
18909
- console.log(`[调试] 提交记录 ${index + 1}:`, {
18910
- 提交ID: commit.id,
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" + error.message ? ", reason: " + error.message || "请求GitLab API失败" : ""
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(({ fields = {}, key: key$1 = "" }) => {
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: [() => Promise.resolve(void 0)]
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(void 0)]
19132
+ report: [() => Promise.resolve()]
19109
19133
  };
19110
19134
  const dingdingReport = {
19111
19135
  title: schemaDingdingTalk,
19112
- report: [() => Promise.resolve(void 0)]
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 && file$1) {
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
- file: file$1,
19145
- line: lines.length > 0 ? lines[lines.length - 1] : void 0,
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", commitAuthor = "" } = issue$1;
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
- let dingdingReportMessage = "";
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
- dingdingReportMessage += `${type === "merge_request" ? `
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 = commitsURL ? commitComments.map(({ sha, file: file$1, line, note }) => () => fetch(`${commitsURL}/${sha}/comments`, {
19232
- method: "POST",
19233
- headers: buildHeaders(),
19234
- body: JSON.stringify({
19235
- note,
19236
- path: file$1,
19237
- line,
19238
- line_type: "new"
19239
- })
19240
- })) : [];
19241
- gitlabReport.report = mergeURL ? [() => fetch(mergeURL + `/notes`, {
19242
- method: "POST",
19243
- headers: buildHeaders(),
19244
- body: JSON.stringify({ body: gitlabReportMessage })
19245
- }).then((res) => res.json()).then((res) => {
19246
- lastComment.value = res;
19247
- })] : CI_COMMIT_SHA ? [() => fetch(`${commitsURL}/${CI_COMMIT_SHA}/comments`, {
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, exit, platform } from "node:process";
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.43";
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
- const visitCommit = () => {
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}/-/commit`;
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 { CI_SERVER_URL, CI_PROJECT_PATH, CI_MERGE_REQUEST_IID = mid, CI_COMMIT_SHA } = envUsed;
133
- if (!CI_SERVER_URL || !CI_PROJECT_PATH) return {};
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: `${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/merge_requests/${CI_MERGE_REQUEST_IID}`,
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 { CI_PROJECT_ID, CI_SERVER_URL } = envUsed;
146
- if (CI_PROJECT_ID && CI_SERVER_URL) return `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/repository/commits`;
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 config = join(HOME, ".copilot/config.json"), dir = join(HOME, ".copilot/skills/codereview");
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", `codereview skills has been updated to version ${versiono}\n\nfor more details, please refer to [ONLINE](https://www.npmjs.com/package/@d5render/cli?activeTab=readme)`);
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") : "") === versiono) return;
207
- const skillRoot = join(RUNTIME_CWD, ".skills/review");
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
- try {
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("Stdout:" + String(chunk)));
285
- copilot.stderr.on("data", (chunk) => console.error("Stderr:" + String(chunk)));
286
- copilot.on("close", (code) => {
287
- if (!code) dingding("CRITICAL", "CI ERROR: 代码审查任务失败,请自行检查日志");
288
- exit(code);
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(`npm list ${platformPackage} -g -p`).toString().trim();
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?.[platformPackage] || copilotPackage.bin?.["@github/copilot"];
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 packagePath = join(npm, "node_modules", ...getCopilotPackageName().split("/"));
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 platformPackage = getCopilotPackageName();
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.43",
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"
@@ -1 +0,0 @@
1
- 0.0.1