@clearlydev/nx-archweave 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/review-verdict.js +5 -248
- package/dist/eslint/index.js +28 -321
- package/dist/generators/_chunks/chunk-2WSX5YNB.js +274 -0
- package/dist/generators/_chunks/chunk-34V6HAJT.js +55 -0
- package/dist/generators/_chunks/chunk-3A6RZUCZ.js +114 -0
- package/dist/generators/_chunks/chunk-47T7NOPP.js +30 -0
- package/dist/generators/_chunks/chunk-5MI3O4VZ.js +99 -0
- package/dist/generators/_chunks/chunk-67INEVWL.js +1068 -0
- package/dist/generators/_chunks/chunk-6ZTIUBB2.js +139 -0
- package/dist/generators/_chunks/chunk-AIJCJ7JK.js +203 -0
- package/dist/generators/_chunks/chunk-AQHV7DJS.js +656 -0
- package/dist/generators/_chunks/chunk-C2VVUWBA.js +386 -0
- package/dist/generators/_chunks/chunk-EXYZLL7A.js +234 -0
- package/dist/generators/_chunks/chunk-FQHFT5IH.js +50 -0
- package/dist/generators/_chunks/chunk-H222FPLK.js +80 -0
- package/dist/generators/_chunks/chunk-IIDOBI42.js +616 -0
- package/dist/generators/_chunks/chunk-JHRTVURZ.js +189 -0
- package/dist/generators/_chunks/chunk-L2RZ2LBO.js +132 -0
- package/dist/generators/_chunks/chunk-MQNQJOWA.js +217 -0
- package/dist/generators/_chunks/chunk-QQNDYJTR.js +243 -0
- package/dist/generators/_chunks/chunk-SFXU6IUS.js +0 -0
- package/dist/generators/_chunks/chunk-SNBIVIMI.js +89 -0
- package/dist/generators/_chunks/chunk-TUN34LAQ.js +8 -0
- package/dist/generators/_chunks/chunk-TXAQ2GJC.js +20 -0
- package/dist/generators/_chunks/chunk-U6LAFHSF.js +49 -0
- package/dist/generators/_chunks/chunk-URV6FLUH.js +73 -0
- package/dist/generators/_chunks/chunk-YNMLB4DL.js +25 -0
- package/dist/generators/_chunks/chunk-YY2UESLM.js +282 -0
- package/dist/generators/add-agent-rules/generator.js +11 -842
- package/dist/generators/add-backend/generator.js +11 -906
- package/dist/generators/add-fsd-lite/generator.js +12 -801
- package/dist/generators/add-github-templates/generator.js +13 -116
- package/dist/generators/add-integration/generator.js +15 -743
- package/dist/generators/add-review/generator.js +12 -1231
- package/dist/generators/allow-builds/generator.js +7 -71
- package/dist/generators/doctor/generator.js +74 -2042
- package/dist/generators/entity/generator.js +15 -604
- package/dist/generators/feature/generator.js +15 -604
- package/dist/generators/init/generator.js +68 -3376
- package/dist/generators/install-integration/generator.js +35 -871
- package/dist/generators/page/generator.js +15 -604
- package/dist/generators/record-integration/generator.js +12 -778
- package/dist/generators/shared/generator.js +15 -604
- package/dist/generators/widget/generator.js +15 -604
- package/dist/index.js +5 -663
- package/package.json +2 -2
|
@@ -0,0 +1,1068 @@
|
|
|
1
|
+
// ../review-policy/dist/renderers/render-review-comment.js
|
|
2
|
+
var COPY = {
|
|
3
|
+
en: {
|
|
4
|
+
terminationBanner: (maxRounds) => `> \u26A0\uFE0F **Automated review ended.** The maximum of ${maxRounds} review rounds has been reached with blocking findings (P0/P1) still open. Later pushes aren't reviewed by AI, so review them yourself before merging.`,
|
|
5
|
+
verdictLines: {
|
|
6
|
+
BLOCK: "**Verdict: BLOCK** \u2014 P0/P1 findings must be resolved before this change can merge.",
|
|
7
|
+
CHANGES_RECOMMENDED: "**Verdict: CHANGES_RECOMMENDED** \u2014 P2 findings should be addressed.",
|
|
8
|
+
APPROVE_WITH_NOTES: "**Verdict: APPROVE_WITH_NOTES** \u2014 only advisory notes below; safe to merge.",
|
|
9
|
+
APPROVE: "**Verdict: APPROVE** \u2014 no findings."
|
|
10
|
+
},
|
|
11
|
+
mainSection: "## Correctness & Security",
|
|
12
|
+
overEngineeringSection: "## Over-engineering",
|
|
13
|
+
nitSection: "## Nits",
|
|
14
|
+
roundHistoryHeading: "**Round history:**",
|
|
15
|
+
roundLine: (round, fixed, remaining) => `- round ${round}: ${fixed} fixed, ${remaining} remaining`,
|
|
16
|
+
fieldLabels: {
|
|
17
|
+
file: "File",
|
|
18
|
+
evidence: "Evidence",
|
|
19
|
+
impact: "Impact",
|
|
20
|
+
reproduction: "Reproduction",
|
|
21
|
+
recommendation: "Recommendation",
|
|
22
|
+
confidence: "Confidence"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
ko: {
|
|
26
|
+
terminationBanner: (maxRounds) => `> \u26A0\uFE0F **\uC790\uB3D9 \uB9AC\uBDF0 \uC885\uB8CC.** \uCD5C\uB300 ${maxRounds}\uD68C\uC758 \uB9AC\uBDF0 \uB77C\uC6B4\uB4DC\uC5D0 \uB3C4\uB2EC\uD588\uC9C0\uB9CC \uBCD1\uD569\uC744 \uB9C9\uB294 finding(P0/P1)\uC774 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uC774\uD6C4 push\uB294 AI\uAC00 \uB9AC\uBDF0\uD558\uC9C0 \uC54A\uC73C\uB2C8, \uBCD1\uD569 \uC804\uC5D0 \uC9C1\uC811 \uB9AC\uBDF0\uD558\uC138\uC694.`,
|
|
27
|
+
verdictLines: {
|
|
28
|
+
BLOCK: "**\uD310\uC815: BLOCK** \u2014 \uBCD1\uD569 \uC804\uC5D0 P0/P1 finding\uC744 \uD574\uACB0\uD574\uC57C \uD569\uB2C8\uB2E4.",
|
|
29
|
+
CHANGES_RECOMMENDED: "**\uD310\uC815: CHANGES_RECOMMENDED** \u2014 P2 finding\uC744 \uD574\uACB0\uD558\uB294 \uAC83\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
|
|
30
|
+
APPROVE_WITH_NOTES: "**\uD310\uC815: APPROVE_WITH_NOTES** \u2014 \uCC38\uACE0\uC6A9 \uB178\uD2B8\uB9CC \uC788\uC2B5\uB2C8\uB2E4. \uBCD1\uD569\uD574\uB3C4 \uC548\uC804\uD569\uB2C8\uB2E4.",
|
|
31
|
+
APPROVE: "**\uD310\uC815: APPROVE** \u2014 finding\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."
|
|
32
|
+
},
|
|
33
|
+
mainSection: "## \uC815\uD655\uC131 & \uBCF4\uC548",
|
|
34
|
+
overEngineeringSection: "## \uACFC\uC789 \uC124\uACC4",
|
|
35
|
+
nitSection: "## \uC0AC\uC18C\uD55C \uC9C0\uC801 (Nit)",
|
|
36
|
+
roundHistoryHeading: "**\uB77C\uC6B4\uB4DC \uC774\uB825:**",
|
|
37
|
+
roundLine: (round, fixed, remaining) => `- ${round}\uB77C\uC6B4\uB4DC: \uD574\uACB0 ${fixed}\uAC74, \uBBF8\uD574\uACB0 ${remaining}\uAC74`,
|
|
38
|
+
fieldLabels: {
|
|
39
|
+
file: "\uD30C\uC77C",
|
|
40
|
+
evidence: "\uADFC\uAC70",
|
|
41
|
+
impact: "\uC601\uD5A5",
|
|
42
|
+
reproduction: "\uC7AC\uD604 \uC870\uAC74",
|
|
43
|
+
recommendation: "\uAD8C\uC7A5 \uC870\uCE58",
|
|
44
|
+
confidence: "\uD655\uC2E0\uB3C4"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function verdictLine(verdict, locale) {
|
|
49
|
+
return COPY[locale].verdictLines[verdict];
|
|
50
|
+
}
|
|
51
|
+
function terminationBannerLine(maxRounds, locale) {
|
|
52
|
+
return COPY[locale].terminationBanner(maxRounds);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ../review-policy/dist/verdict-gate/verdict-comment.js
|
|
56
|
+
var VERDICT_MARKER = "<!-- archweave-verdict -->";
|
|
57
|
+
var HISTORY_HEADING = { en: "**Round history:**", ko: "**\uB77C\uC6B4\uB4DC \uC774\uB825:**" };
|
|
58
|
+
var ROUND_LINE = {
|
|
59
|
+
en: (round, fixed, remaining) => `- round ${round}: ${fixed} fixed, ${remaining} remaining`,
|
|
60
|
+
ko: (round, fixed, remaining) => `- ${round}\uCC28: ${fixed}\uAC74 \uD574\uACB0, ${remaining}\uAC74 \uC794\uC874`
|
|
61
|
+
};
|
|
62
|
+
var UNPARSEABLE_NOTICE = {
|
|
63
|
+
en: "\u26A0\uFE0F The review comment could not be machine-read (missing or invalid findings block). The verdict gate stays green; please review this PR manually.",
|
|
64
|
+
ko: "\u26A0\uFE0F \uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uAE30\uACC4 \uD310\uB3C5\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(findings \uBE14\uB85D \uB204\uB77D \uB610\uB294 \uC190\uC0C1). verdict \uAC8C\uC774\uD2B8\uB294 \uB179\uC0C9\uC73C\uB85C \uB450\uB2C8 \uC774 PR\uC740 \uC0AC\uB78C\uC774 \uC9C1\uC811 \uB9AC\uBDF0\uD574 \uC8FC\uC138\uC694."
|
|
65
|
+
};
|
|
66
|
+
var NO_ROUND_NOTICE = {
|
|
67
|
+
en: "\u26A0\uFE0F This run of the review posted no summary comment for the current commit, so there is no verdict for it (an earlier round is never reused). The verdict gate stays green; please review this PR manually.",
|
|
68
|
+
ko: "\u26A0\uFE0F \uC774\uBC88 \uB9AC\uBDF0 \uC2E4\uD589\uC774 \uD604\uC7AC \uCEE4\uBC0B\uC5D0 \uB300\uD55C \uC694\uC57D \uCF54\uBA58\uD2B8\uB97C \uAC8C\uC2DC\uD558\uC9C0 \uC54A\uC544 verdict\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4(\uC774\uC804 \uB77C\uC6B4\uB4DC\uB294 \uC7AC\uC0AC\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4). verdict \uAC8C\uC774\uD2B8\uB294 \uB179\uC0C9\uC73C\uB85C \uB450\uB2C8 \uC774 PR\uC740 \uC0AC\uB78C\uC774 \uC9C1\uC811 \uB9AC\uBDF0\uD574 \uC8FC\uC138\uC694."
|
|
69
|
+
};
|
|
70
|
+
var NO_SUMMARY_NOTICE = {
|
|
71
|
+
en: "\u26D4 AI review posted no summary for this commit, so there is no verdict for it and the review check fails. Review this PR manually, or re-run all jobs to review it again.",
|
|
72
|
+
ko: "\u26D4 AI \uB9AC\uBDF0\uAC00 \uC774 \uCEE4\uBC0B\uC5D0 \uB300\uD55C \uC694\uC57D\uC744 \uAC8C\uC2DC\uD558\uC9C0 \uC54A\uC544 verdict\uAC00 \uC5C6\uACE0, review \uCCB4\uD06C\uB294 \uC2E4\uD328\uD569\uB2C8\uB2E4. \uC774 PR\uC744 \uC9C1\uC811 \uB9AC\uBDF0\uD558\uAC70\uB098, \uBAA8\uB4E0 \uC7A1\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uB2E4\uC2DC \uB9AC\uBDF0\uBC1B\uC73C\uC138\uC694."
|
|
73
|
+
};
|
|
74
|
+
var UNREADABLE_SUMMARY_NOTICE = {
|
|
75
|
+
en: "\u26D4 AI review posted a summary whose findings block could not be machine-read (missing or invalid), so there is no verdict for this commit and the review check fails. Review this PR manually, or re-run all jobs to review it again.",
|
|
76
|
+
ko: "\u26D4 AI \uB9AC\uBDF0 \uC694\uC57D\uC758 findings \uBE14\uB85D\uC744 \uAE30\uACC4 \uD310\uB3C5\uD558\uC9C0 \uBABB\uD574(\uB204\uB77D \uB610\uB294 \uC190\uC0C1) \uC774 \uCEE4\uBC0B\uC758 verdict\uAC00 \uC5C6\uACE0, review \uCCB4\uD06C\uB294 \uC2E4\uD328\uD569\uB2C8\uB2E4. \uC774 PR\uC744 \uC9C1\uC811 \uB9AC\uBDF0\uD558\uAC70\uB098, \uBAA8\uB4E0 \uC7A1\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uB2E4\uC2DC \uB9AC\uBDF0\uBC1B\uC73C\uC138\uC694."
|
|
77
|
+
};
|
|
78
|
+
function renderCommitLine(locale, shortSha) {
|
|
79
|
+
return locale === "en" ? `<sub>Commit \`${shortSha}\` \xB7 this comment is updated on every review round.</sub>` : `<sub>\uCEE4\uBC0B \`${shortSha}\` \xB7 \uC774 \uCF54\uBA58\uD2B8\uB294 \uB9AC\uBDF0 \uB77C\uC6B4\uB4DC\uB9C8\uB2E4 \uAC31\uC2E0\uB429\uB2C8\uB2E4.</sub>`;
|
|
80
|
+
}
|
|
81
|
+
var HEAD_LINE = {
|
|
82
|
+
en: (sha) => renderCommitLine("en", sha.slice(0, 7)),
|
|
83
|
+
ko: (sha) => renderCommitLine("ko", sha.slice(0, 7))
|
|
84
|
+
};
|
|
85
|
+
var NOT_RUN_REASON = {
|
|
86
|
+
en: {
|
|
87
|
+
secret: (secretName) => `the ${secretName} secret is not registered`,
|
|
88
|
+
fork: () => "this pull request comes from a fork, and GitHub passes no secrets to runs from forks",
|
|
89
|
+
dependabot: (secretName) => `Dependabot runs receive only Dependabot secrets, and ${secretName} is not one of them`,
|
|
90
|
+
rounds: (_secretName, maxRounds) => `the automated review round limit (${maxRounds}) is reached`
|
|
91
|
+
},
|
|
92
|
+
ko: {
|
|
93
|
+
secret: (secretName) => `${secretName} \uC2DC\uD06C\uB9BF\uC774 \uB4F1\uB85D\uB418\uC9C0 \uC54A\uC74C`,
|
|
94
|
+
fork: () => "\uD3EC\uD06C\uC5D0\uC11C \uC628 pull request\uC774\uACE0, GitHub\uB294 \uD3EC\uD06C \uC2E4\uD589\uC5D0 \uC2DC\uD06C\uB9BF\uC744 \uB118\uAE30\uC9C0 \uC54A\uC74C",
|
|
95
|
+
dependabot: (secretName) => `Dependabot \uC2E4\uD589\uC740 Dependabot \uC2DC\uD06C\uB9BF\uB9CC \uBC1B\uB294\uB370, ${secretName}\uB294 \uADF8\uC911\uC5D0 \uC5C6\uC74C`,
|
|
96
|
+
rounds: (_secretName, maxRounds) => `\uC790\uB3D9 \uB9AC\uBDF0 \uB77C\uC6B4\uB4DC \uD55C\uB3C4(${maxRounds})\uC5D0 \uB3C4\uB2EC\uD568`
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function renderNotRunNotice(locale, reason, secretName, maxRounds) {
|
|
100
|
+
const why = NOT_RUN_REASON[locale][reason](secretName, maxRounds);
|
|
101
|
+
return locale === "en" ? `AI review did not run for this commit (${why}). A green check here does not mean the change was reviewed.` : `\uC774 \uCEE4\uBC0B\uC5D0\uB294 AI \uB9AC\uBDF0\uAC00 \uC2E4\uD589\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4(${why}). \uC774 green \uCCB4\uD06C\uB294 \uBCC0\uACBD \uC0AC\uD56D\uC774 \uB9AC\uBDF0\uB418\uC5C8\uB2E4\uB294 \uB73B\uC774 \uC544\uB2D9\uB2C8\uB2E4.`;
|
|
102
|
+
}
|
|
103
|
+
function withHead(lines, locale, headSha) {
|
|
104
|
+
return (headSha === void 0 ? lines : [...lines, "", HEAD_LINE[locale](headSha)]).join("\n");
|
|
105
|
+
}
|
|
106
|
+
function renderUnparseableNotice(locale, headSha, failClosed = false) {
|
|
107
|
+
return withHead([VERDICT_MARKER, (failClosed ? UNREADABLE_SUMMARY_NOTICE : UNPARSEABLE_NOTICE)[locale]], locale, headSha);
|
|
108
|
+
}
|
|
109
|
+
function renderNoSummaryNotice(locale, headSha) {
|
|
110
|
+
return withHead([VERDICT_MARKER, NO_SUMMARY_NOTICE[locale]], locale, headSha);
|
|
111
|
+
}
|
|
112
|
+
function renderNoRoundNotice(locale, headSha) {
|
|
113
|
+
return withHead([VERDICT_MARKER, NO_ROUND_NOTICE[locale]], locale, headSha);
|
|
114
|
+
}
|
|
115
|
+
function renderVerdictComment(input) {
|
|
116
|
+
const lines = [VERDICT_MARKER];
|
|
117
|
+
if (input.isFinalRound && input.verdict === "BLOCK") {
|
|
118
|
+
lines.push(terminationBannerLine(input.maxRounds, input.locale), "");
|
|
119
|
+
}
|
|
120
|
+
lines.push(verdictLine(input.verdict, input.locale));
|
|
121
|
+
if (input.history.length > 1) {
|
|
122
|
+
lines.push("", HISTORY_HEADING[input.locale]);
|
|
123
|
+
for (let i = 0; i < input.history.length; i++) {
|
|
124
|
+
if (i === 0)
|
|
125
|
+
continue;
|
|
126
|
+
const entry = input.history[i];
|
|
127
|
+
lines.push(ROUND_LINE[input.locale](entry.round, entry.fixedFindingKeys.length, entry.remainingFindingKeys.length));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return withHead(lines, input.locale, input.headSha);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ../review-policy/dist/schema/review-finding.js
|
|
134
|
+
function computeVerdict(findings) {
|
|
135
|
+
if (findings.some((f) => f.priority === "P0" || f.priority === "P1"))
|
|
136
|
+
return "BLOCK";
|
|
137
|
+
if (findings.some((f) => f.priority === "P2"))
|
|
138
|
+
return "CHANGES_RECOMMENDED";
|
|
139
|
+
if (findings.length > 0)
|
|
140
|
+
return "APPROVE_WITH_NOTES";
|
|
141
|
+
return "APPROVE";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ../review-policy/dist/verdict-gate/findings-block.js
|
|
145
|
+
var FINDINGS_MARKER = "<!-- archweave-findings-v1 -->";
|
|
146
|
+
var FINDINGS_SCHEMA_ID = "archweave/findings@1";
|
|
147
|
+
var PRIORITIES = ["P0", "P1", "P2", "P3", "Nit"];
|
|
148
|
+
function findingKey(f) {
|
|
149
|
+
return `${f.file}:${f.lineRange.start}:${f.category}`;
|
|
150
|
+
}
|
|
151
|
+
function extractFindingsBlock(commentBody) {
|
|
152
|
+
const markerIndex = commentBody.lastIndexOf(FINDINGS_MARKER);
|
|
153
|
+
if (markerIndex === -1)
|
|
154
|
+
return { status: "missing" };
|
|
155
|
+
const after = commentBody.slice(markerIndex + FINDINGS_MARKER.length);
|
|
156
|
+
const open = /```json[ \t]*\r?\n/.exec(after);
|
|
157
|
+
if (!open)
|
|
158
|
+
return { status: "invalid" };
|
|
159
|
+
const contentStart = open.index + open[0].length;
|
|
160
|
+
let parsed;
|
|
161
|
+
let found = false;
|
|
162
|
+
for (let close = after.indexOf("```", contentStart); close !== -1; close = after.indexOf("```", close + 3)) {
|
|
163
|
+
try {
|
|
164
|
+
parsed = JSON.parse(after.slice(contentStart, close));
|
|
165
|
+
found = true;
|
|
166
|
+
break;
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!found)
|
|
171
|
+
return { status: "invalid" };
|
|
172
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
173
|
+
return { status: "invalid" };
|
|
174
|
+
const obj = parsed;
|
|
175
|
+
if (obj.schema !== FINDINGS_SCHEMA_ID || !Array.isArray(obj.findings))
|
|
176
|
+
return { status: "invalid" };
|
|
177
|
+
const findings = [];
|
|
178
|
+
for (const raw of obj.findings) {
|
|
179
|
+
const finding = normalizeFinding(raw);
|
|
180
|
+
if (finding === null)
|
|
181
|
+
return { status: "invalid" };
|
|
182
|
+
findings.push(finding);
|
|
183
|
+
}
|
|
184
|
+
return { status: "ok", findings };
|
|
185
|
+
}
|
|
186
|
+
function normalizeFinding(raw) {
|
|
187
|
+
if (raw === null || typeof raw !== "object")
|
|
188
|
+
return null;
|
|
189
|
+
const r = raw;
|
|
190
|
+
const lineRange = r["lineRange"];
|
|
191
|
+
if (!PRIORITIES.includes(r["priority"]) || typeof r["category"] !== "string" || typeof r["title"] !== "string" || typeof r["file"] !== "string" || lineRange === null || lineRange === void 0 || typeof lineRange !== "object" || typeof lineRange.start !== "number" || !Number.isInteger(lineRange.start) || lineRange.start < 1) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const str = (value) => typeof value === "string" ? value : "";
|
|
195
|
+
return {
|
|
196
|
+
priority: r["priority"],
|
|
197
|
+
category: r["category"],
|
|
198
|
+
title: r["title"],
|
|
199
|
+
file: r["file"],
|
|
200
|
+
lineRange: { start: lineRange.start, ...typeof lineRange.end === "number" ? { end: lineRange.end } : {} },
|
|
201
|
+
evidence: str(r["evidence"]),
|
|
202
|
+
impact: str(r["impact"]),
|
|
203
|
+
reproductionCondition: str(r["reproductionCondition"]),
|
|
204
|
+
recommendation: str(r["recommendation"]),
|
|
205
|
+
confidence: r["confidence"] === "plausible" ? "plausible" : "confirmed"
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ../review-policy/dist/verdict-gate/verdict-gate.js
|
|
210
|
+
var ROUND_MARKER = "<!-- archweave-review-round -->";
|
|
211
|
+
function collectRounds(comments) {
|
|
212
|
+
return comments.filter((c) => c.body.includes(ROUND_MARKER) && !c.body.includes(VERDICT_MARKER)).map((c, index) => ({ round: index + 1, id: c.id, extraction: extractFindingsBlock(c.body) }));
|
|
213
|
+
}
|
|
214
|
+
var LINE_DRIFT = 3;
|
|
215
|
+
function sameDefect(a, b) {
|
|
216
|
+
if (a.file !== b.file)
|
|
217
|
+
return false;
|
|
218
|
+
const aEnd = a.lineRange.end ?? a.lineRange.start;
|
|
219
|
+
const bEnd = b.lineRange.end ?? b.lineRange.start;
|
|
220
|
+
return a.lineRange.start <= bEnd + LINE_DRIFT && b.lineRange.start <= aEnd + LINE_DRIFT;
|
|
221
|
+
}
|
|
222
|
+
function computeRoundHistory(rounds) {
|
|
223
|
+
const parsed = rounds.filter((r) => r.extraction.status === "ok");
|
|
224
|
+
const history = [];
|
|
225
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
226
|
+
const keys = new Set(parsed[i].extraction.findings.map(findingKey));
|
|
227
|
+
if (i === 0) {
|
|
228
|
+
history.push({ round: parsed[i].round, fixedFindingKeys: [], remainingFindingKeys: [...keys] });
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const current = parsed[i].extraction.findings;
|
|
232
|
+
history.push({
|
|
233
|
+
round: parsed[i].round,
|
|
234
|
+
fixedFindingKeys: parsed[i - 1].extraction.findings.filter((finding) => !current.some((now) => sameDefect(finding, now))).map(findingKey),
|
|
235
|
+
remainingFindingKeys: [...keys]
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return history;
|
|
239
|
+
}
|
|
240
|
+
function runVerdictGate(input) {
|
|
241
|
+
const rounds = collectRounds(input.comments);
|
|
242
|
+
const reviewRan = input.reviewRan === true;
|
|
243
|
+
if (rounds.length === 0) {
|
|
244
|
+
return reviewRan ? { kind: "no-summary", commentBody: renderNoSummaryNotice(input.locale, input.headSha) } : { kind: "no-review" };
|
|
245
|
+
}
|
|
246
|
+
const latest = rounds[rounds.length - 1];
|
|
247
|
+
if (input.roundAfterId !== void 0 && (latest.id === void 0 || latest.id <= input.roundAfterId)) {
|
|
248
|
+
return reviewRan ? { kind: "no-summary", commentBody: renderNoSummaryNotice(input.locale, input.headSha) } : { kind: "no-round", commentBody: renderNoRoundNotice(input.locale, input.headSha) };
|
|
249
|
+
}
|
|
250
|
+
if (latest.extraction.status !== "ok") {
|
|
251
|
+
return { kind: "unparseable", commentBody: renderUnparseableNotice(input.locale, input.headSha, reviewRan), failClosed: reviewRan };
|
|
252
|
+
}
|
|
253
|
+
const verdict = computeVerdict(latest.extraction.findings);
|
|
254
|
+
const commentBody = renderVerdictComment({
|
|
255
|
+
verdict,
|
|
256
|
+
history: computeRoundHistory(rounds),
|
|
257
|
+
maxRounds: input.maxRounds,
|
|
258
|
+
locale: input.locale,
|
|
259
|
+
isFinalRound: rounds.length >= input.maxRounds,
|
|
260
|
+
headSha: input.headSha
|
|
261
|
+
});
|
|
262
|
+
return { kind: "verdict", verdict, commentBody, exitCode: verdict === "BLOCK" ? 1 : 0 };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ../template-engine/dist/review/config-snippet.js
|
|
266
|
+
function buildReviewConfigSnippet(input) {
|
|
267
|
+
return {
|
|
268
|
+
enabled: true,
|
|
269
|
+
provider: input.provider,
|
|
270
|
+
mode: input.mode,
|
|
271
|
+
locale: input.locale,
|
|
272
|
+
maxRounds: input.maxRounds,
|
|
273
|
+
blockingPriorities: ["P0", "P1"]
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ../review-policy/dist/schema/review-policy-config.js
|
|
278
|
+
var DEFAULT_MAX_ROUNDS = 3;
|
|
279
|
+
|
|
280
|
+
// ../review-policy/dist/modes/review-modes.js
|
|
281
|
+
var REVIEW_MODES = {
|
|
282
|
+
fast: {
|
|
283
|
+
mode: "fast",
|
|
284
|
+
promptStyle: "fast-condensed",
|
|
285
|
+
includesCorrectnessSecurity: true,
|
|
286
|
+
includesOverEngineering: false
|
|
287
|
+
},
|
|
288
|
+
standard: {
|
|
289
|
+
mode: "standard",
|
|
290
|
+
promptStyle: "sectioned",
|
|
291
|
+
includesCorrectnessSecurity: true,
|
|
292
|
+
includesOverEngineering: false
|
|
293
|
+
},
|
|
294
|
+
thorough: {
|
|
295
|
+
mode: "thorough",
|
|
296
|
+
promptStyle: "sectioned",
|
|
297
|
+
includesCorrectnessSecurity: true,
|
|
298
|
+
includesOverEngineering: true
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// ../review-policy/dist/prompts/prompt-shared.js
|
|
303
|
+
function renderFindingSchemaInstruction(locale, priorities) {
|
|
304
|
+
const intro = locale === "en" ? "For every finding, fill in every field of this schema:" : "\uBAA8\uB4E0 finding\uC5D0 \uB300\uD574 \uC544\uB798 \uC2A4\uD0A4\uB9C8\uC758 \uBAA8\uB4E0 \uD544\uB4DC\uB97C \uCC44\uC6B0\uC138\uC694:";
|
|
305
|
+
return [
|
|
306
|
+
intro,
|
|
307
|
+
`- priority: ${priorities.join(" | ")}`,
|
|
308
|
+
"- category: kebab-case category name",
|
|
309
|
+
"- title: one-line summary",
|
|
310
|
+
"- file: path of the affected file",
|
|
311
|
+
"- lineRange: { start, end? }",
|
|
312
|
+
"- evidence: what in the code proves the problem",
|
|
313
|
+
"- impact: what goes wrong and for whom",
|
|
314
|
+
"- reproductionCondition: the exact input/state that triggers it",
|
|
315
|
+
"- recommendation: the concrete fix",
|
|
316
|
+
"- confidence: confirmed | plausible"
|
|
317
|
+
].join("\n");
|
|
318
|
+
}
|
|
319
|
+
function renderFindingsBlockInstruction(locale) {
|
|
320
|
+
const lines = locale === "en" ? [
|
|
321
|
+
"At the END of the top-level summary comment, append the machine-readable findings block:",
|
|
322
|
+
`the literal marker line ${FINDINGS_MARKER}, then a fenced \`\`\`json block containing`,
|
|
323
|
+
`{ "schema": "${FINDINGS_SCHEMA_ID}", "findings": [ ... ] } with one entry per finding,`,
|
|
324
|
+
"using exactly the finding schema above. If there are no findings, still append the block with an empty findings array."
|
|
325
|
+
] : [
|
|
326
|
+
"top-level \uC694\uC57D \uCF54\uBA58\uD2B8\uC758 \uB9E8 \uB05D\uC5D0 \uAE30\uACC4 \uD310\uB3C5\uC6A9 findings \uBE14\uB85D\uC744 \uB367\uBD99\uC774\uC138\uC694:",
|
|
327
|
+
`\uB9AC\uD130\uB7F4 \uB9C8\uCEE4 \uC904 ${FINDINGS_MARKER} \uB2E4\uC74C\uC5D0 fenced \`\`\`json \uBE14\uB85D\uC73C\uB85C`,
|
|
328
|
+
`{ "schema": "${FINDINGS_SCHEMA_ID}", "findings": [ ... ] }\uB97C, finding\uB9C8\uB2E4 \uD55C \uC6D0\uC18C\uC529, \uC704 finding \uC2A4\uD0A4\uB9C8 \uADF8\uB300\uB85C \uC791\uC131\uD558\uC138\uC694.`,
|
|
329
|
+
"finding\uC774 \uC5C6\uC5B4\uB3C4 \uBE48 \uBC30\uC5F4\uB85C \uBE14\uB85D\uC744 \uBC18\uB4DC\uC2DC \uD3EC\uD568\uD558\uC138\uC694."
|
|
330
|
+
];
|
|
331
|
+
return lines.join("\n");
|
|
332
|
+
}
|
|
333
|
+
function renderDedupInstruction(locale) {
|
|
334
|
+
return locale === "en" ? "If multiple findings share the same root cause, merge them into a single finding instead of reporting duplicates." : "\uAC19\uC740 \uC6D0\uC778\uC5D0\uC11C \uB098\uC628 finding\uC774 \uC5EC\uB7EC \uAC1C\uB77C\uBA74 \uC911\uBCF5 \uBCF4\uACE0\uD558\uC9C0 \uB9D0\uACE0 \uD558\uB098\uC758 finding\uC73C\uB85C \uD569\uCCD0\uC11C \uBCF4\uACE0\uD558\uC138\uC694.";
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ../review-policy/dist/prompts/correctness-security-prompt.js
|
|
338
|
+
var CORRECTNESS_SECURITY_PRIORITIES = ["P0", "P1", "P2", "Nit"];
|
|
339
|
+
var FAST_PRIORITIES = ["P0", "P1", "P2"];
|
|
340
|
+
function buildCorrectnessSecurityPrompt(locale, style = "sectioned") {
|
|
341
|
+
if (style === "fast-condensed") {
|
|
342
|
+
const fastBody = locale === "en" ? [
|
|
343
|
+
"You are a code reviewer focused on correctness and security.",
|
|
344
|
+
"",
|
|
345
|
+
"Review the provided diff and report only the most critical problems. Skip minor style and taste remarks entirely \u2014 report nothing below P2.",
|
|
346
|
+
"",
|
|
347
|
+
`Only report findings with priority ${FAST_PRIORITIES.join(", ")}. Do NOT report P3 (over-engineering) findings \u2014 they are out of scope here.`
|
|
348
|
+
] : [
|
|
349
|
+
"\uB2F9\uC2E0\uC740 \uC815\uD655\uC131(correctness)\uACFC \uBCF4\uC548(security)\uC5D0 \uC9D1\uC911\uD558\uB294 \uCF54\uB4DC \uB9AC\uBDF0\uC5B4\uC785\uB2C8\uB2E4.",
|
|
350
|
+
"",
|
|
351
|
+
"\uC8FC\uC5B4\uC9C4 diff\uC5D0\uC11C \uAC00\uC7A5 \uCE58\uBA85\uC801\uC778 \uBB38\uC81C\uB9CC \uBCF4\uACE0\uD558\uC138\uC694. \uC0AC\uC18C\uD55C \uC2A4\uD0C0\uC77C\xB7\uCDE8\uD5A5 \uC9C0\uC801\uC740 \uC644\uC804\uD788 \uC0DD\uB7B5\uD558\uACE0, P2 \uBBF8\uB9CC\uC740 \uBCF4\uACE0\uD558\uC9C0 \uB9C8\uC138\uC694.",
|
|
352
|
+
"",
|
|
353
|
+
`\uC6B0\uC120\uC21C\uC704 ${FAST_PRIORITIES.join(", ")}\uC5D0 \uD574\uB2F9\uD558\uB294 finding\uB9CC \uBCF4\uACE0\uD558\uC138\uC694. P3(\uACFC\uC789 \uC124\uACC4)\uB294 \uBCF4\uACE0\uD558\uC9C0 \uB9C8\uC138\uC694 \u2014 \uC774 \uB9AC\uBDF0\uC758 \uBC94\uC704 \uBC16\uC785\uB2C8\uB2E4.`
|
|
354
|
+
];
|
|
355
|
+
return [...fastBody, "", renderFindingSchemaInstruction(locale, FAST_PRIORITIES), "", renderDedupInstruction(locale)].join("\n");
|
|
356
|
+
}
|
|
357
|
+
const body = locale === "en" ? [
|
|
358
|
+
"You are a code reviewer focused on correctness and security.",
|
|
359
|
+
"",
|
|
360
|
+
'Review the provided diff and report findings in two explicitly separated top-level sections: "Correctness" and "Security". Never mix the two.',
|
|
361
|
+
"",
|
|
362
|
+
`Only report findings with priority ${CORRECTNESS_SECURITY_PRIORITIES.join(", ")}. Do NOT report P3 (over-engineering) findings \u2014 they are covered by a separate reviewer and are out of scope here.`
|
|
363
|
+
] : [
|
|
364
|
+
"\uB2F9\uC2E0\uC740 \uC815\uD655\uC131(correctness)\uACFC \uBCF4\uC548(security)\uC5D0 \uC9D1\uC911\uD558\uB294 \uCF54\uB4DC \uB9AC\uBDF0\uC5B4\uC785\uB2C8\uB2E4.",
|
|
365
|
+
"",
|
|
366
|
+
'\uC8FC\uC5B4\uC9C4 diff\uB97C \uB9AC\uBDF0\uD558\uACE0, finding\uC744 "Correctness"\uC640 "Security" \uB450 \uAC1C\uC758 \uCD5C\uC0C1\uC704 \uC139\uC158\uC73C\uB85C \uBD84\uB9AC\uD574 \uBCF4\uACE0\uD558\uC138\uC694. \uB450 \uC139\uC158\uC744 \uC808\uB300 \uC11E\uC9C0 \uB9C8\uC138\uC694.',
|
|
367
|
+
"",
|
|
368
|
+
`\uC6B0\uC120\uC21C\uC704 ${CORRECTNESS_SECURITY_PRIORITIES.join(", ")}\uC5D0 \uD574\uB2F9\uD558\uB294 finding\uB9CC \uBCF4\uACE0\uD558\uC138\uC694. P3\uB294 \uBCF4\uACE0\uD558\uC9C0 \uB9C8\uC138\uC694 \u2014 P3(\uACFC\uC789 \uC124\uACC4)\uB294 \uBCC4\uB3C4\uC758 \uB9AC\uBDF0\uC5B4\uAC00 \uB2F4\uB2F9\uD558\uBBC0\uB85C \uC774 \uB9AC\uBDF0\uC758 \uBC94\uC704 \uBC16\uC785\uB2C8\uB2E4.`
|
|
369
|
+
];
|
|
370
|
+
return [...body, "", renderFindingSchemaInstruction(locale, CORRECTNESS_SECURITY_PRIORITIES), "", renderDedupInstruction(locale)].join("\n");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ../review-policy/dist/prompts/over-engineering-prompt.js
|
|
374
|
+
var OVER_ENGINEERING_PRIORITIES = ["P3"];
|
|
375
|
+
function buildOverEngineeringPrompt(locale) {
|
|
376
|
+
const body = locale === "en" ? [
|
|
377
|
+
"You are a code reviewer focused exclusively on over-engineering.",
|
|
378
|
+
"",
|
|
379
|
+
"Review the provided diff and report findings with priority P3 only. Do NOT report P0, P1, P2, or Nit findings \u2014 correctness and security are covered by a separate reviewer.",
|
|
380
|
+
"",
|
|
381
|
+
'Report your findings in their own top-level section titled "Over-engineering"; never mix them into correctness or security sections.'
|
|
382
|
+
] : [
|
|
383
|
+
"\uB2F9\uC2E0\uC740 \uACFC\uC789 \uC124\uACC4(over-engineering)\uB9CC\uC744 \uC804\uB2F4\uD558\uB294 \uCF54\uB4DC \uB9AC\uBDF0\uC5B4\uC785\uB2C8\uB2E4.",
|
|
384
|
+
"",
|
|
385
|
+
"\uC8FC\uC5B4\uC9C4 diff\uB97C \uB9AC\uBDF0\uD558\uACE0 \uC6B0\uC120\uC21C\uC704 P3\uB9CC \uBCF4\uACE0\uD558\uC138\uC694. P0, P1, P2, Nit\uC740 \uBCF4\uACE0\uD558\uC9C0 \uB9C8\uC138\uC694 \u2014 \uC815\uD655\uC131\uACFC \uBCF4\uC548\uC740 \uBCC4\uB3C4\uC758 \uB9AC\uBDF0\uC5B4\uAC00 \uB2F4\uB2F9\uD569\uB2C8\uB2E4.",
|
|
386
|
+
"",
|
|
387
|
+
'finding\uC740 "Over-engineering"\uC774\uB77C\uB294 \uBCC4\uB3C4\uC758 \uCD5C\uC0C1\uC704 \uC139\uC158\uC73C\uB85C \uBCF4\uACE0\uD558\uACE0, correctness/security \uC139\uC158\uACFC \uC808\uB300 \uC11E\uC9C0 \uB9C8\uC138\uC694.'
|
|
388
|
+
];
|
|
389
|
+
return [...body, "", renderFindingSchemaInstruction(locale, OVER_ENGINEERING_PRIORITIES), "", renderDedupInstruction(locale)].join("\n");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ../template-engine/dist/review/action-pins.js
|
|
393
|
+
var ACTION_PINS = {
|
|
394
|
+
"actions/checkout": "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1",
|
|
395
|
+
"actions/setup-node": "actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0",
|
|
396
|
+
"pnpm/action-setup": "pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0",
|
|
397
|
+
"oven-sh/setup-bun": "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0",
|
|
398
|
+
"anthropics/claude-code-action": "anthropics/claude-code-action@6b6870e649b5ff408753befeaee5fc7989d4e6a9 # v1.0.258",
|
|
399
|
+
"google-github-actions/run-gemini-cli": "google-github-actions/run-gemini-cli@f77273f4c914e4bf38440cf36a0369cb64a37489 # v0.1.22",
|
|
400
|
+
"openai/codex-action": "openai/codex-action@bdf19a4a223ec2549a3e2274a0cf61556bc07675 # v1.13"
|
|
401
|
+
};
|
|
402
|
+
function pinnedUses(action) {
|
|
403
|
+
return ACTION_PINS[action];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ../template-engine/dist/review/workflow-skeleton.js
|
|
407
|
+
function indentBlock(text, spaces) {
|
|
408
|
+
const pad = " ".repeat(spaces);
|
|
409
|
+
return text.split("\n").map((line) => line.length === 0 ? line : pad + line).join("\n");
|
|
410
|
+
}
|
|
411
|
+
function renderTriggerBlock() {
|
|
412
|
+
return ["on:", " pull_request:", " types: [opened, synchronize, ready_for_review, reopened]"].join("\n");
|
|
413
|
+
}
|
|
414
|
+
function renderConcurrencyBlock() {
|
|
415
|
+
return [
|
|
416
|
+
"concurrency:",
|
|
417
|
+
" group: archweave-review-${{ github.event.pull_request.number }}",
|
|
418
|
+
" cancel-in-progress: false"
|
|
419
|
+
].join("\n");
|
|
420
|
+
}
|
|
421
|
+
var REVIEW_GATE_CONDITION = "steps.secret-gate.outputs.enabled == 'true' && steps.round-gate.outputs.allowed == 'true'";
|
|
422
|
+
function renderReviewPermissions(idToken) {
|
|
423
|
+
return ["permissions:", " contents: read", " pull-requests: write", ...idToken ? [" id-token: write"] : []].join("\n");
|
|
424
|
+
}
|
|
425
|
+
function renderCheckoutStep(gateCondition, extraWith = []) {
|
|
426
|
+
return [
|
|
427
|
+
"- name: Checkout repository",
|
|
428
|
+
...gateCondition === void 0 ? [] : [` if: ${gateCondition}`],
|
|
429
|
+
` uses: ${pinnedUses("actions/checkout")}`,
|
|
430
|
+
" with:",
|
|
431
|
+
...extraWith.map((line) => ` ${line}`),
|
|
432
|
+
" fetch-depth: 1",
|
|
433
|
+
" persist-credentials: false"
|
|
434
|
+
].join("\n");
|
|
435
|
+
}
|
|
436
|
+
function renderSecretGateStep(secretName) {
|
|
437
|
+
return [
|
|
438
|
+
"- name: Check review secret",
|
|
439
|
+
" id: secret-gate",
|
|
440
|
+
" env:",
|
|
441
|
+
` REVIEW_SECRET: \${{ secrets.${secretName} }}`,
|
|
442
|
+
" run: |",
|
|
443
|
+
' if [ -z "$REVIEW_SECRET" ]; then',
|
|
444
|
+
` echo "AI review is not active: register the ${secretName} secret to enable it."`,
|
|
445
|
+
' echo "enabled=false" >> "$GITHUB_OUTPUT"',
|
|
446
|
+
" else",
|
|
447
|
+
' echo "enabled=true" >> "$GITHUB_OUTPUT"',
|
|
448
|
+
" fi"
|
|
449
|
+
].join("\n");
|
|
450
|
+
}
|
|
451
|
+
function renderRoundGateStep(maxRounds) {
|
|
452
|
+
return [
|
|
453
|
+
"- name: Check review round limit",
|
|
454
|
+
" id: round-gate",
|
|
455
|
+
" if: steps.secret-gate.outputs.enabled == 'true'",
|
|
456
|
+
" env:",
|
|
457
|
+
" GH_TOKEN: ${{ github.token }}",
|
|
458
|
+
" run: |",
|
|
459
|
+
` counts=$(gh api "repos/\${{ github.repository }}/issues/\${{ github.event.pull_request.number }}/comments" --paginate --jq '[([.[] | select(.user.type == "Bot") | select(.body | contains("${ROUND_MARKER}"))] | length), ([.[].id] | max // 0)]' | jq -s -r '[(map(.[0]) | add // 0), (map(.[1]) | max // 0)] | @tsv')`,
|
|
460
|
+
` rounds=$(printf '%s' "$counts" | cut -f1)`,
|
|
461
|
+
` echo "last-comment-id=$(printf '%s' "$counts" | cut -f2)" >> "$GITHUB_OUTPUT"`,
|
|
462
|
+
` if [ "$rounds" -ge ${maxRounds} ]; then`,
|
|
463
|
+
` echo "Automated review round limit (${maxRounds}) reached for this PR; skipping."`,
|
|
464
|
+
' echo "allowed=false" >> "$GITHUB_OUTPUT"',
|
|
465
|
+
" else",
|
|
466
|
+
' echo "allowed=true" >> "$GITHUB_OUTPUT"',
|
|
467
|
+
" fi"
|
|
468
|
+
].join("\n");
|
|
469
|
+
}
|
|
470
|
+
function shellQuote(text) {
|
|
471
|
+
return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
472
|
+
}
|
|
473
|
+
function renderNotRunFunction(secretName, maxRounds, locale) {
|
|
474
|
+
const reasons = ["secret", "fork", "dependabot", "rounds"];
|
|
475
|
+
const [commitBefore, commitAfter] = renderCommitLine(locale, "\0").split("\0");
|
|
476
|
+
return [
|
|
477
|
+
"not_run() {",
|
|
478
|
+
' case "$1" in',
|
|
479
|
+
...reasons.map((reason) => ` ${reason}) annotation=${shellQuote(renderNotRunNotice("en", reason, secretName, maxRounds))}; notice=${shellQuote(renderNotRunNotice(locale, reason, secretName, maxRounds))} ;;`),
|
|
480
|
+
" esac",
|
|
481
|
+
' echo "::notice title=AI review did not run::$annotation"',
|
|
482
|
+
` printf '%s\\n' "$notice" >> "$GITHUB_STEP_SUMMARY"`,
|
|
483
|
+
` short_sha=$(printf '%s' "$HEAD_SHA" | cut -c1-7)`,
|
|
484
|
+
' case "$short_sha" in',
|
|
485
|
+
" '' | *[!0-9a-f]*) short_sha= ;;",
|
|
486
|
+
" esac",
|
|
487
|
+
" {",
|
|
488
|
+
' echo "notice-body<<ARCHWEAVE_NOT_RUN_EOF"',
|
|
489
|
+
` printf '%s\\n' ${shellQuote(VERDICT_MARKER)} "\u2139\uFE0F $notice"`,
|
|
490
|
+
` if [ -n "$short_sha" ]; then printf '\\n%s\\n' ${shellQuote(commitBefore)}"$short_sha"${shellQuote(commitAfter)}; fi`,
|
|
491
|
+
' echo "ARCHWEAVE_NOT_RUN_EOF"',
|
|
492
|
+
' echo "run=false"',
|
|
493
|
+
' } >> "$GITHUB_OUTPUT"',
|
|
494
|
+
"}"
|
|
495
|
+
];
|
|
496
|
+
}
|
|
497
|
+
var JUDGE_CONDITION = "steps.ai-review-result.outputs.run == 'true'";
|
|
498
|
+
function renderAiReviewResultStep(secretName, maxRounds, locale) {
|
|
499
|
+
return [
|
|
500
|
+
"- name: Check the AI review result",
|
|
501
|
+
" id: ai-review-result",
|
|
502
|
+
" env:",
|
|
503
|
+
" AI_REVIEW_RESULT: ${{ needs.ai-review.result }}",
|
|
504
|
+
" REVIEW_ENABLED: ${{ needs.ai-review.outputs.enabled }}",
|
|
505
|
+
" REVIEW_ALLOWED: ${{ needs.ai-review.outputs.allowed }}",
|
|
506
|
+
" LAST_COMMENT_ID: ${{ needs.ai-review.outputs.last-comment-id }}",
|
|
507
|
+
" HEAD_SHA: ${{ github.event.pull_request.head.sha }}",
|
|
508
|
+
" HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}",
|
|
509
|
+
" REPOSITORY: ${{ github.repository }}",
|
|
510
|
+
" PR_AUTHOR: ${{ github.event.pull_request.user.login }}",
|
|
511
|
+
" run: |",
|
|
512
|
+
...renderNotRunFunction(secretName, maxRounds, locale).map((line) => ` ${line}`),
|
|
513
|
+
' if [ "$AI_REVIEW_RESULT" != success ]; then',
|
|
514
|
+
` echo "::error title=AI review did not succeed::The AI review job ended with '$AI_REVIEW_RESULT'."`,
|
|
515
|
+
" exit 1",
|
|
516
|
+
" fi",
|
|
517
|
+
' case "$REVIEW_ENABLED/$REVIEW_ALLOWED" in',
|
|
518
|
+
" false/*)",
|
|
519
|
+
' if [ "$HEAD_REPOSITORY" != "$REPOSITORY" ]; then not_run fork',
|
|
520
|
+
` elif [ "$PR_AUTHOR" = 'dependabot[bot]' ]; then not_run dependabot`,
|
|
521
|
+
" else not_run secret",
|
|
522
|
+
" fi",
|
|
523
|
+
" exit 0",
|
|
524
|
+
" ;;",
|
|
525
|
+
" true/false)",
|
|
526
|
+
" not_run rounds",
|
|
527
|
+
" exit 0",
|
|
528
|
+
" ;;",
|
|
529
|
+
" true/true)",
|
|
530
|
+
' case "$LAST_COMMENT_ID" in',
|
|
531
|
+
" '' | *[!0-9]*) ;;",
|
|
532
|
+
" *)",
|
|
533
|
+
' echo "run=true" >> "$GITHUB_OUTPUT"',
|
|
534
|
+
" exit 0",
|
|
535
|
+
" ;;",
|
|
536
|
+
" esac",
|
|
537
|
+
" ;;",
|
|
538
|
+
" esac",
|
|
539
|
+
` echo "::error title=Review verdict not computed::The AI review job's results did not reach this job, so no verdict can be computed. Re-run all jobs."`,
|
|
540
|
+
" exit 1"
|
|
541
|
+
].join("\n");
|
|
542
|
+
}
|
|
543
|
+
function renderVerdictGateStep(maxRounds, locale) {
|
|
544
|
+
return [
|
|
545
|
+
"- name: Compute review verdict",
|
|
546
|
+
" id: verdict",
|
|
547
|
+
` if: ${JUDGE_CONDITION}`,
|
|
548
|
+
" env:",
|
|
549
|
+
" GH_TOKEN: ${{ github.token }}",
|
|
550
|
+
" GITHUB_REPOSITORY: ${{ github.repository }}",
|
|
551
|
+
" ARCHWEAVE_PR_NUMBER: ${{ github.event.pull_request.number }}",
|
|
552
|
+
` ARCHWEAVE_MAX_ROUNDS: '${maxRounds}'`,
|
|
553
|
+
` ARCHWEAVE_LOCALE: '${locale}'`,
|
|
554
|
+
" ARCHWEAVE_HEAD_SHA: ${{ github.event.pull_request.head.sha }}",
|
|
555
|
+
" ARCHWEAVE_ROUND_AFTER_ID: ${{ needs.ai-review.outputs.last-comment-id }}",
|
|
556
|
+
" ARCHWEAVE_VERDICT_OUTPUT: 'github-output'",
|
|
557
|
+
" run: |",
|
|
558
|
+
" if [ -x node_modules/.bin/archweave-review-verdict ]; then",
|
|
559
|
+
" node_modules/.bin/archweave-review-verdict",
|
|
560
|
+
" else",
|
|
561
|
+
' echo "nx-archweave is not installed in this workspace - skipping the verdict gate."',
|
|
562
|
+
" fi"
|
|
563
|
+
].join("\n");
|
|
564
|
+
}
|
|
565
|
+
function renderBlockStep() {
|
|
566
|
+
return [
|
|
567
|
+
"- name: Fail the review check",
|
|
568
|
+
" if: steps.verdict.outputs.blocked == 'true'",
|
|
569
|
+
" env:",
|
|
570
|
+
" BLOCK_REASON: ${{ steps.verdict.outputs.block-reason }}",
|
|
571
|
+
" run: |",
|
|
572
|
+
' case "$BLOCK_REASON" in',
|
|
573
|
+
' block) echo "The AI verdict is BLOCK: fix the P0/P1 findings in the verdict comment and push." ;;',
|
|
574
|
+
' no-summary) echo "The AI review posted no summary for this commit, so there is no verdict. Re-run all jobs to review it again, or review it yourself." ;;',
|
|
575
|
+
' unreadable) echo "The AI review summary has no readable findings block, so there is no verdict. Re-run all jobs to review it again, or review it yourself." ;;',
|
|
576
|
+
' *) echo "The review check failed. See the verdict comment on this pull request." ;;',
|
|
577
|
+
" esac",
|
|
578
|
+
" exit 1"
|
|
579
|
+
].join("\n");
|
|
580
|
+
}
|
|
581
|
+
function renderVerdictCommentStep() {
|
|
582
|
+
return [
|
|
583
|
+
"- name: Post review verdict",
|
|
584
|
+
" env:",
|
|
585
|
+
" GH_TOKEN: ${{ github.token }}",
|
|
586
|
+
" REPOSITORY: ${{ github.repository }}",
|
|
587
|
+
" PR_NUMBER: ${{ github.event.pull_request.number }}",
|
|
588
|
+
" VERDICT_BODY: ${{ needs.review.outputs.comment-body }}",
|
|
589
|
+
" PREVIOUS_ID: ${{ needs.review.outputs.previous-comment-id }}",
|
|
590
|
+
" run: |",
|
|
591
|
+
` payload=$(jq -n --arg body "$VERDICT_BODY" '{body: $body}')`,
|
|
592
|
+
' if [ -z "$PREVIOUS_ID" ]; then',
|
|
593
|
+
` PREVIOUS_ID=$(gh api "repos/$REPOSITORY/issues/$PR_NUMBER/comments" --paginate --jq '.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("${VERDICT_MARKER}")) | .id' | tail -n 1) || PREVIOUS_ID=`,
|
|
594
|
+
" fi",
|
|
595
|
+
' case "$PREVIOUS_ID" in',
|
|
596
|
+
" ''|*[!0-9]*) PREVIOUS_ID= ;;",
|
|
597
|
+
" esac",
|
|
598
|
+
` if [ -n "$PREVIOUS_ID" ] && printf '%s' "$payload" | gh api -X PATCH "repos/$REPOSITORY/issues/comments/$PREVIOUS_ID" --input - > /dev/null; then`,
|
|
599
|
+
" exit 0",
|
|
600
|
+
" fi",
|
|
601
|
+
` printf '%s' "$payload" | gh api -X POST "repos/$REPOSITORY/issues/$PR_NUMBER/comments" --input - > /dev/null \\`,
|
|
602
|
+
' || echo "Could not post the verdict comment (continuing)."'
|
|
603
|
+
].join("\n");
|
|
604
|
+
}
|
|
605
|
+
var SETUP_NODE_VERSION = "22";
|
|
606
|
+
var FALLBACK_PNPM_VERSION = "10";
|
|
607
|
+
var INSTALL_LINES = {
|
|
608
|
+
pnpm: [" pnpm install --frozen-lockfile --ignore-scripts"],
|
|
609
|
+
npm: [" npm ci --ignore-scripts"],
|
|
610
|
+
yarn: [
|
|
611
|
+
" corepack enable yarn",
|
|
612
|
+
' if [ "$(yarn --version | cut -d. -f1)" -ge 2 ]; then YARN_ENABLE_SCRIPTS=false yarn install --immutable --mode=skip-build; else yarn install --frozen-lockfile --ignore-scripts; fi'
|
|
613
|
+
],
|
|
614
|
+
bun: [" bun install --frozen-lockfile --ignore-scripts"]
|
|
615
|
+
};
|
|
616
|
+
function renderPnpmVersionStep(gate) {
|
|
617
|
+
return [
|
|
618
|
+
"- name: Detect pnpm version",
|
|
619
|
+
" id: pnpm-version",
|
|
620
|
+
gate,
|
|
621
|
+
" run: |",
|
|
622
|
+
` if [ -n "$(jq -r '.packageManager // empty' package.json)" ]; then`,
|
|
623
|
+
' echo "version=" >> "$GITHUB_OUTPUT"',
|
|
624
|
+
" else",
|
|
625
|
+
` echo "version=${FALLBACK_PNPM_VERSION}" >> "$GITHUB_OUTPUT"`,
|
|
626
|
+
" fi"
|
|
627
|
+
].join("\n");
|
|
628
|
+
}
|
|
629
|
+
function renderWorkspaceSetupSteps(packageManager) {
|
|
630
|
+
const gate = ` if: ${JUDGE_CONDITION}`;
|
|
631
|
+
const steps = [];
|
|
632
|
+
if (packageManager === "pnpm") {
|
|
633
|
+
steps.push(renderPnpmVersionStep(gate));
|
|
634
|
+
steps.push([
|
|
635
|
+
"- name: Set up pnpm",
|
|
636
|
+
gate,
|
|
637
|
+
` uses: ${pinnedUses("pnpm/action-setup")}`,
|
|
638
|
+
" with:",
|
|
639
|
+
" version: ${{ steps.pnpm-version.outputs.version }}"
|
|
640
|
+
].join("\n"));
|
|
641
|
+
}
|
|
642
|
+
if (packageManager === "bun") {
|
|
643
|
+
steps.push(["- name: Set up bun", gate, ` uses: ${pinnedUses("oven-sh/setup-bun")}`].join("\n"));
|
|
644
|
+
}
|
|
645
|
+
steps.push([
|
|
646
|
+
"- name: Set up Node",
|
|
647
|
+
gate,
|
|
648
|
+
` uses: ${pinnedUses("actions/setup-node")}`,
|
|
649
|
+
" with:",
|
|
650
|
+
` node-version: ${SETUP_NODE_VERSION}`,
|
|
651
|
+
// yarn: no setup-node cache — it would query the runner's global yarn 1 even in a
|
|
652
|
+
// berry project. Corepack (shipped with Node) provides the project's own yarn below.
|
|
653
|
+
// bun: setup-node has no bun cache. Node is still set up — nx and the verdict bin
|
|
654
|
+
// run on it.
|
|
655
|
+
...packageManager === "yarn" || packageManager === "bun" ? [] : [` cache: ${packageManager}`]
|
|
656
|
+
].join("\n"));
|
|
657
|
+
steps.push([
|
|
658
|
+
"- name: Install workspace dependencies",
|
|
659
|
+
gate,
|
|
660
|
+
" run: |",
|
|
661
|
+
...INSTALL_LINES[packageManager]
|
|
662
|
+
].join("\n"));
|
|
663
|
+
return steps.join("\n\n");
|
|
664
|
+
}
|
|
665
|
+
var REVIEW_JOB_TIMEOUT_MINUTES = 15;
|
|
666
|
+
function renderReviewWorkflow(input, parts) {
|
|
667
|
+
const aiReviewSteps = [renderSecretGateStep(parts.secretName), renderRoundGateStep(input.maxRounds), ...parts.reviewSteps];
|
|
668
|
+
const reviewSteps = [
|
|
669
|
+
renderAiReviewResultStep(parts.secretName, input.maxRounds, input.locale),
|
|
670
|
+
renderCheckoutStep(JUDGE_CONDITION),
|
|
671
|
+
renderWorkspaceSetupSteps(input.packageManager),
|
|
672
|
+
renderVerdictGateStep(input.maxRounds, input.locale),
|
|
673
|
+
renderBlockStep()
|
|
674
|
+
];
|
|
675
|
+
return [
|
|
676
|
+
`name: ${parts.name}`,
|
|
677
|
+
"",
|
|
678
|
+
renderTriggerBlock(),
|
|
679
|
+
"",
|
|
680
|
+
renderConcurrencyBlock(),
|
|
681
|
+
"",
|
|
682
|
+
"permissions: {}",
|
|
683
|
+
"",
|
|
684
|
+
"jobs:",
|
|
685
|
+
" ai-review:",
|
|
686
|
+
" runs-on: ubuntu-latest",
|
|
687
|
+
` timeout-minutes: ${REVIEW_JOB_TIMEOUT_MINUTES}`,
|
|
688
|
+
indentBlock(renderReviewPermissions(parts.idToken), 4),
|
|
689
|
+
" outputs:",
|
|
690
|
+
" enabled: ${{ steps.secret-gate.outputs.enabled }}",
|
|
691
|
+
" allowed: ${{ steps.round-gate.outputs.allowed }}",
|
|
692
|
+
" last-comment-id: ${{ steps.round-gate.outputs.last-comment-id }}",
|
|
693
|
+
" steps:",
|
|
694
|
+
indentBlock(aiReviewSteps.join("\n\n"), 6),
|
|
695
|
+
"",
|
|
696
|
+
" review:",
|
|
697
|
+
" needs: ai-review",
|
|
698
|
+
" if: always()",
|
|
699
|
+
" runs-on: ubuntu-latest",
|
|
700
|
+
` timeout-minutes: ${REVIEW_JOB_TIMEOUT_MINUTES}`,
|
|
701
|
+
" permissions:",
|
|
702
|
+
" contents: read",
|
|
703
|
+
" pull-requests: read",
|
|
704
|
+
" outputs:",
|
|
705
|
+
" comment-body: ${{ steps.verdict.outputs.comment-body || steps.ai-review-result.outputs.notice-body }}",
|
|
706
|
+
" previous-comment-id: ${{ steps.verdict.outputs.previous-comment-id }}",
|
|
707
|
+
" steps:",
|
|
708
|
+
indentBlock(reviewSteps.join("\n\n"), 6),
|
|
709
|
+
"",
|
|
710
|
+
" review-comment:",
|
|
711
|
+
" needs: review",
|
|
712
|
+
// Runs after a BLOCK failed the review job, too: the comment explains the red check.
|
|
713
|
+
" if: ${{ !cancelled() && needs.review.outputs.comment-body != '' }}",
|
|
714
|
+
" runs-on: ubuntu-latest",
|
|
715
|
+
` timeout-minutes: ${REVIEW_JOB_TIMEOUT_MINUTES}`,
|
|
716
|
+
" permissions:",
|
|
717
|
+
" pull-requests: write",
|
|
718
|
+
" steps:",
|
|
719
|
+
indentBlock(renderVerdictCommentStep(), 6),
|
|
720
|
+
""
|
|
721
|
+
].join("\n");
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// ../template-engine/dist/review/claude/claude-provider.js
|
|
725
|
+
var SECRET_NAME = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
726
|
+
var ALLOWED_TOOLS = "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)";
|
|
727
|
+
var CLAUDE_STEP_ID = "claude-review";
|
|
728
|
+
var PR_CONTEXT = ["REPO: ${{ github.repository }}", "PR NUMBER: ${{ github.event.pull_request.number }}"];
|
|
729
|
+
function renderWorkflowInstructions(locale) {
|
|
730
|
+
const lines = locale === "en" ? [
|
|
731
|
+
...PR_CONTEXT,
|
|
732
|
+
"",
|
|
733
|
+
"Review the diff of this pull request using `gh pr diff <PR NUMBER>` and `gh pr view <PR NUMBER>`.",
|
|
734
|
+
"Post specific findings as inline comments with `mcp__github_inline_comment__create_inline_comment` (pass `confirmed: true`), and exactly one top-level summary comment.",
|
|
735
|
+
"Post the summary with `gh pr comment <PR NUMBER> --body-file - <<'ARCHWEAVE_EOF'`, the body on the following lines, then a line `ARCHWEAVE_EOF` (the quoted delimiter keeps backticks and `$` literal). Never pass the body with `--body \"...\"` or `$(...)`.",
|
|
736
|
+
"Run each `gh` command on its own: no pipes to other programs, no `&&` chains.",
|
|
737
|
+
`Always include the literal marker ${ROUND_MARKER} in the top-level summary comment.`,
|
|
738
|
+
"If there are no findings that require action, post exactly one top-level comment saying so (and still include the marker).",
|
|
739
|
+
"Do not modify any code, and never approve or merge the pull request.",
|
|
740
|
+
"Write summaries and explanations in English; keep file names, function names, and identifiers verbatim."
|
|
741
|
+
] : [
|
|
742
|
+
...PR_CONTEXT,
|
|
743
|
+
"",
|
|
744
|
+
"\uC774 pull request\uC758 diff\uB97C `gh pr diff <PR NUMBER>`\uC640 `gh pr view <PR NUMBER>`\uB85C \uD655\uC778\uD558\uACE0 \uB9AC\uBDF0\uD558\uC138\uC694.",
|
|
745
|
+
"\uAD6C\uCCB4\uC801 \uC9C0\uC801\uC740 `mcp__github_inline_comment__create_inline_comment`(`confirmed: true` \uC804\uB2EC)\uB85C inline comment\uB97C \uB2EC\uACE0, \uC804\uCCB4 \uC694\uC57D\uC740 \uC815\uD655\uD788 \uD55C \uAC1C\uC758 top-level comment\uB85C \uAC8C\uC2DC\uD558\uC138\uC694.",
|
|
746
|
+
"\uC694\uC57D\uC740 `gh pr comment <PR NUMBER> --body-file - <<'ARCHWEAVE_EOF'`\uB85C \uAC8C\uC2DC\uD558\uC138\uC694. \uB2E4\uC74C \uC904\uBD80\uD130 \uBCF8\uBB38\uC744 \uC4F0\uACE0, \uB9C8\uC9C0\uB9C9 \uC904\uC5D0 `ARCHWEAVE_EOF`\uB9CC \uC4F0\uC138\uC694(\uB530\uC634\uD45C \uCE5C \uAD6C\uBD84\uC790\uB77C \uBC31\uD2F1\uACFC `$`\uAC00 \uADF8\uB300\uB85C \uB0A8\uC544\uC694). \uBCF8\uBB38\uC744 `--body \"...\"`\uB098 `$(...)`\uB85C \uB118\uAE30\uC9C0 \uB9C8\uC138\uC694.",
|
|
747
|
+
"`gh` \uBA85\uB839\uC740 \uD558\uB098\uC529 \uB530\uB85C \uC2E4\uD589\uD558\uC138\uC694. \uB2E4\uB978 \uD504\uB85C\uADF8\uB7A8\uC73C\uB85C \uD30C\uC774\uD504\uD558\uAC70\uB098 `&&`\uB85C \uC787\uC9C0 \uB9C8\uC138\uC694.",
|
|
748
|
+
`top-level \uC694\uC57D \uCF54\uBA58\uD2B8\uC5D0\uB294 \uBC18\uB4DC\uC2DC \uB9AC\uD130\uB7F4 \uB9C8\uCEE4 ${ROUND_MARKER}\uB97C \uD3EC\uD568\uD558\uC138\uC694.`,
|
|
749
|
+
'\uC870\uCE58\uAC00 \uD544\uC694\uD55C \uBB38\uC81C\uAC00 \uC5C6\uC73C\uBA74 "\uC870\uCE58\uAC00 \uD544\uC694\uD55C \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."\uB77C\uB294 top-level comment\uB97C \uC815\uD655\uD788 \uD55C \uAC1C \uAC8C\uC2DC\uD558\uC138\uC694(\uC774\uB54C\uB3C4 \uB9C8\uCEE4\uB294 \uD3EC\uD568).',
|
|
750
|
+
"\uCF54\uB4DC\uB97C \uC218\uC815\uD558\uC9C0 \uB9C8\uC138\uC694(Do not modify any code). PR\uC744 \uC2B9\uC778\uD558\uAC70\uB098 \uBCD1\uD569\uD558\uC9C0 \uB9C8\uC138\uC694(never approve or merge).",
|
|
751
|
+
"\uC694\uC57D\uACFC \uC124\uBA85\uC740 \uD55C\uAD6D\uC5B4\uB85C \uC4F0\uB418, \uD30C\uC77C\uBA85\xB7\uD568\uC218\uBA85\xB7\uC2DD\uBCC4\uC790\uB294 \uC6D0\uBB38 \uADF8\uB300\uB85C \uC720\uC9C0\uD558\uC138\uC694."
|
|
752
|
+
];
|
|
753
|
+
return lines.join("\n");
|
|
754
|
+
}
|
|
755
|
+
function composePrompt(input) {
|
|
756
|
+
const mode = REVIEW_MODES[input.mode];
|
|
757
|
+
const sections = [renderWorkflowInstructions(input.locale), buildCorrectnessSecurityPrompt(input.locale, mode.promptStyle)];
|
|
758
|
+
if (mode.includesOverEngineering) {
|
|
759
|
+
sections.push(buildOverEngineeringPrompt(input.locale));
|
|
760
|
+
}
|
|
761
|
+
sections.push(renderFindingsBlockInstruction(input.locale));
|
|
762
|
+
return sections.join("\n\n---\n\n");
|
|
763
|
+
}
|
|
764
|
+
function renderDeniedToolsStep() {
|
|
765
|
+
return [
|
|
766
|
+
"- name: Show denied tool calls",
|
|
767
|
+
` if: always() && steps.${CLAUDE_STEP_ID}.outputs.execution_file != ''`,
|
|
768
|
+
" env:",
|
|
769
|
+
` EXECUTION_FILE: \${{ steps.${CLAUDE_STEP_ID}.outputs.execution_file }}`,
|
|
770
|
+
" run: |",
|
|
771
|
+
' [ -f "$EXECUTION_FILE" ] || exit 0',
|
|
772
|
+
` stop_token="archweave-$(od -An -N16 -tx1 /dev/urandom | tr -d ' \\n')"`,
|
|
773
|
+
' echo "::stop-commands::$stop_token"',
|
|
774
|
+
` jq -c '.[] | select(.type == "result") | ((.permission_denials[]? | {tool: .tool_name, command: ((.tool_input.command // "") | tostring | .[0:200])}), (select(.is_error == true) | {error: ((.result // "") | tostring | .[0:300])}))' "$EXECUTION_FILE" || echo "Could not read the execution file."`,
|
|
775
|
+
' echo "::$stop_token::"'
|
|
776
|
+
].join("\n");
|
|
777
|
+
}
|
|
778
|
+
function buildWorkflowYaml(input) {
|
|
779
|
+
return renderReviewWorkflow(input, {
|
|
780
|
+
name: "Claude Code Review",
|
|
781
|
+
secretName: SECRET_NAME,
|
|
782
|
+
// The Claude GitHub App signs in with the workflow's OIDC token (claude-code-action's FAQ).
|
|
783
|
+
idToken: true,
|
|
784
|
+
reviewSteps: [
|
|
785
|
+
renderCheckoutStep(REVIEW_GATE_CONDITION),
|
|
786
|
+
[
|
|
787
|
+
"- name: Run Claude review",
|
|
788
|
+
` id: ${CLAUDE_STEP_ID}`,
|
|
789
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
790
|
+
` uses: ${pinnedUses("anthropics/claude-code-action")}`,
|
|
791
|
+
" with:",
|
|
792
|
+
` claude_code_oauth_token: \${{ secrets.${SECRET_NAME} }}`,
|
|
793
|
+
" prompt: |",
|
|
794
|
+
indentBlock(composePrompt(input), 6),
|
|
795
|
+
" claude_args: |",
|
|
796
|
+
` --allowedTools "${ALLOWED_TOOLS}"`
|
|
797
|
+
].join("\n"),
|
|
798
|
+
renderDeniedToolsStep()
|
|
799
|
+
]
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
var claudeProvider = {
|
|
803
|
+
id: "claude",
|
|
804
|
+
workflowFileName: ".github/workflows/claude-code-review.yml",
|
|
805
|
+
requiredSecretName: SECRET_NAME,
|
|
806
|
+
buildWorkflowYaml
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
// ../template-engine/dist/review/cursor/cursor-provider.js
|
|
810
|
+
var SECRET_NAME2 = "CURSOR_API_KEY";
|
|
811
|
+
var PROMPT_HEREDOC_DELIMITER = "ARCHWEAVE_CURSOR_REVIEW_PROMPT";
|
|
812
|
+
function renderWorkflowInstructions2(locale) {
|
|
813
|
+
const lines = locale === "en" ? [
|
|
814
|
+
"Review the diff of this pull request using `gh pr diff` and `gh pr view`.",
|
|
815
|
+
"Post specific findings as inline comments and exactly one top-level summary comment.",
|
|
816
|
+
`Always include the literal marker ${ROUND_MARKER} in the top-level summary comment.`,
|
|
817
|
+
"If there are no findings that require action, post exactly one top-level comment saying so (and still include the marker).",
|
|
818
|
+
"Do not modify any code, and never approve or merge the pull request.",
|
|
819
|
+
"Write summaries and explanations in English; keep file names, function names, and identifiers verbatim."
|
|
820
|
+
] : [
|
|
821
|
+
"\uC774 pull request\uC758 diff\uB97C `gh pr diff`\uC640 `gh pr view`\uB85C \uD655\uC778\uD558\uACE0 \uB9AC\uBDF0\uD558\uC138\uC694.",
|
|
822
|
+
"\uAD6C\uCCB4\uC801 \uC9C0\uC801\uC740 inline comment\uB85C, \uC804\uCCB4 \uC694\uC57D\uC740 \uC815\uD655\uD788 \uD55C \uAC1C\uC758 top-level comment\uB85C \uAC8C\uC2DC\uD558\uC138\uC694.",
|
|
823
|
+
`top-level \uC694\uC57D \uCF54\uBA58\uD2B8\uC5D0\uB294 \uBC18\uB4DC\uC2DC \uB9AC\uD130\uB7F4 \uB9C8\uCEE4 ${ROUND_MARKER}\uB97C \uD3EC\uD568\uD558\uC138\uC694.`,
|
|
824
|
+
'\uC870\uCE58\uAC00 \uD544\uC694\uD55C \uBB38\uC81C\uAC00 \uC5C6\uC73C\uBA74 "\uC870\uCE58\uAC00 \uD544\uC694\uD55C \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."\uB77C\uB294 top-level comment\uB97C \uC815\uD655\uD788 \uD55C \uAC1C \uAC8C\uC2DC\uD558\uC138\uC694(\uC774\uB54C\uB3C4 \uB9C8\uCEE4\uB294 \uD3EC\uD568).',
|
|
825
|
+
"\uCF54\uB4DC\uB97C \uC218\uC815\uD558\uC9C0 \uB9C8\uC138\uC694(Do not modify any code). PR\uC744 \uC2B9\uC778\uD558\uAC70\uB098 \uBCD1\uD569\uD558\uC9C0 \uB9C8\uC138\uC694(never approve or merge).",
|
|
826
|
+
"\uC694\uC57D\uACFC \uC124\uBA85\uC740 \uD55C\uAD6D\uC5B4\uB85C \uC4F0\uB418, \uD30C\uC77C\uBA85\xB7\uD568\uC218\uBA85\xB7\uC2DD\uBCC4\uC790\uB294 \uC6D0\uBB38 \uADF8\uB300\uB85C \uC720\uC9C0\uD558\uC138\uC694."
|
|
827
|
+
];
|
|
828
|
+
return lines.join("\n");
|
|
829
|
+
}
|
|
830
|
+
function composePrompt2(input) {
|
|
831
|
+
const mode = REVIEW_MODES[input.mode];
|
|
832
|
+
const sections = [renderWorkflowInstructions2(input.locale), buildCorrectnessSecurityPrompt(input.locale, mode.promptStyle)];
|
|
833
|
+
if (mode.includesOverEngineering) {
|
|
834
|
+
sections.push(buildOverEngineeringPrompt(input.locale));
|
|
835
|
+
}
|
|
836
|
+
sections.push(renderFindingsBlockInstruction(input.locale));
|
|
837
|
+
return sections.join("\n\n---\n\n");
|
|
838
|
+
}
|
|
839
|
+
function buildWorkflowYaml2(input) {
|
|
840
|
+
return renderReviewWorkflow(input, {
|
|
841
|
+
name: "Cursor Code Review",
|
|
842
|
+
secretName: SECRET_NAME2,
|
|
843
|
+
// An API key: the Cursor CLI needs no OIDC token.
|
|
844
|
+
idToken: false,
|
|
845
|
+
reviewSteps: [
|
|
846
|
+
renderCheckoutStep(REVIEW_GATE_CONDITION),
|
|
847
|
+
[
|
|
848
|
+
"- name: Install cursor-agent CLI",
|
|
849
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
850
|
+
" run: |",
|
|
851
|
+
// HTTPS only, TLS 1.2 or newer. Cursor publishes no pinned version or checksum for
|
|
852
|
+
// this installer; docs/pr-review.md states what that leaves open.
|
|
853
|
+
" curl --proto '=https' --tlsv1.2 -fsS https://cursor.com/install | bash",
|
|
854
|
+
// The docs name ~/.cursor/bin; the installer currently links `agent` into ~/.local/bin.
|
|
855
|
+
// Both go on the PATH, so the next step finds it wherever the installer puts it.
|
|
856
|
+
' echo "$HOME/.local/bin" >> "$GITHUB_PATH"',
|
|
857
|
+
' echo "$HOME/.cursor/bin" >> "$GITHUB_PATH"'
|
|
858
|
+
].join("\n"),
|
|
859
|
+
[
|
|
860
|
+
"- name: Run Cursor review",
|
|
861
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
862
|
+
" env:",
|
|
863
|
+
` CURSOR_API_KEY: \${{ secrets.${SECRET_NAME2} }}`,
|
|
864
|
+
" GH_TOKEN: ${{ github.token }}",
|
|
865
|
+
" run: |",
|
|
866
|
+
// cursor-agent stops at "Workspace Trust Required" without --trust (2026.10.22 on
|
|
867
|
+
// Actions); the folder is this run's own checkout, and forks never get the secret.
|
|
868
|
+
` agent --trust -p "$(cat <<'${PROMPT_HEREDOC_DELIMITER}'`,
|
|
869
|
+
indentBlock(composePrompt2(input), 4),
|
|
870
|
+
` ${PROMPT_HEREDOC_DELIMITER}`,
|
|
871
|
+
' )"'
|
|
872
|
+
].join("\n")
|
|
873
|
+
]
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
var cursorProvider = {
|
|
877
|
+
id: "cursor",
|
|
878
|
+
workflowFileName: ".github/workflows/cursor-code-review.yml",
|
|
879
|
+
requiredSecretName: SECRET_NAME2,
|
|
880
|
+
buildWorkflowYaml: buildWorkflowYaml2
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// ../template-engine/dist/review/gemini/gemini-provider.js
|
|
884
|
+
var SECRET_NAME3 = "GEMINI_API_KEY";
|
|
885
|
+
function renderWorkflowInstructions3(locale) {
|
|
886
|
+
const lines = locale === "en" ? [
|
|
887
|
+
"Review the diff of this pull request using `gh pr diff` and `gh pr view`.",
|
|
888
|
+
"Post specific findings as inline comments and exactly one top-level summary comment.",
|
|
889
|
+
`Always include the literal marker ${ROUND_MARKER} in the top-level summary comment.`,
|
|
890
|
+
"If there are no findings that require action, post exactly one top-level comment saying so (and still include the marker).",
|
|
891
|
+
"Do not modify any code, and never approve or merge the pull request.",
|
|
892
|
+
"Write summaries and explanations in English; keep file names, function names, and identifiers verbatim."
|
|
893
|
+
] : [
|
|
894
|
+
"\uC774 pull request\uC758 diff\uB97C `gh pr diff`\uC640 `gh pr view`\uB85C \uD655\uC778\uD558\uACE0 \uB9AC\uBDF0\uD558\uC138\uC694.",
|
|
895
|
+
"\uAD6C\uCCB4\uC801 \uC9C0\uC801\uC740 inline comment\uB85C, \uC804\uCCB4 \uC694\uC57D\uC740 \uC815\uD655\uD788 \uD55C \uAC1C\uC758 top-level comment\uB85C \uAC8C\uC2DC\uD558\uC138\uC694.",
|
|
896
|
+
`top-level \uC694\uC57D \uCF54\uBA58\uD2B8\uC5D0\uB294 \uBC18\uB4DC\uC2DC \uB9AC\uD130\uB7F4 \uB9C8\uCEE4 ${ROUND_MARKER}\uB97C \uD3EC\uD568\uD558\uC138\uC694.`,
|
|
897
|
+
'\uC870\uCE58\uAC00 \uD544\uC694\uD55C \uBB38\uC81C\uAC00 \uC5C6\uC73C\uBA74 "\uC870\uCE58\uAC00 \uD544\uC694\uD55C \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."\uB77C\uB294 top-level comment\uB97C \uC815\uD655\uD788 \uD55C \uAC1C \uAC8C\uC2DC\uD558\uC138\uC694(\uC774\uB54C\uB3C4 \uB9C8\uCEE4\uB294 \uD3EC\uD568).',
|
|
898
|
+
"\uCF54\uB4DC\uB97C \uC218\uC815\uD558\uC9C0 \uB9C8\uC138\uC694(Do not modify any code). PR\uC744 \uC2B9\uC778\uD558\uAC70\uB098 \uBCD1\uD569\uD558\uC9C0 \uB9C8\uC138\uC694(never approve or merge).",
|
|
899
|
+
"\uC694\uC57D\uACFC \uC124\uBA85\uC740 \uD55C\uAD6D\uC5B4\uB85C \uC4F0\uB418, \uD30C\uC77C\uBA85\xB7\uD568\uC218\uBA85\xB7\uC2DD\uBCC4\uC790\uB294 \uC6D0\uBB38 \uADF8\uB300\uB85C \uC720\uC9C0\uD558\uC138\uC694."
|
|
900
|
+
];
|
|
901
|
+
return lines.join("\n");
|
|
902
|
+
}
|
|
903
|
+
function composePrompt3(input) {
|
|
904
|
+
const mode = REVIEW_MODES[input.mode];
|
|
905
|
+
const sections = [renderWorkflowInstructions3(input.locale), buildCorrectnessSecurityPrompt(input.locale, mode.promptStyle)];
|
|
906
|
+
if (mode.includesOverEngineering) {
|
|
907
|
+
sections.push(buildOverEngineeringPrompt(input.locale));
|
|
908
|
+
}
|
|
909
|
+
sections.push(renderFindingsBlockInstruction(input.locale));
|
|
910
|
+
return sections.join("\n\n---\n\n");
|
|
911
|
+
}
|
|
912
|
+
var GEMINI_CLI_VERSION = "0.65.0";
|
|
913
|
+
function buildWorkflowYaml3(input) {
|
|
914
|
+
return renderReviewWorkflow(input, {
|
|
915
|
+
name: "Gemini Code Review",
|
|
916
|
+
secretName: SECRET_NAME3,
|
|
917
|
+
// An API key: run-gemini-cli uses OIDC only for Google Cloud workload identity federation.
|
|
918
|
+
idToken: false,
|
|
919
|
+
reviewSteps: [
|
|
920
|
+
renderCheckoutStep(REVIEW_GATE_CONDITION),
|
|
921
|
+
[
|
|
922
|
+
"- name: Run Gemini review",
|
|
923
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
924
|
+
` uses: ${pinnedUses("google-github-actions/run-gemini-cli")}`,
|
|
925
|
+
" env:",
|
|
926
|
+
" GITHUB_TOKEN: ${{ github.token }}",
|
|
927
|
+
// Gemini CLI 0.65 refuses to run in a folder it hasn't been told to trust (measured
|
|
928
|
+
// 2026-10-25 on Actions). The folder is this run's own checkout; pull requests from forks
|
|
929
|
+
// never get here, because they get no secret.
|
|
930
|
+
" GEMINI_CLI_TRUST_WORKSPACE: 'true'",
|
|
931
|
+
" with:",
|
|
932
|
+
` gemini_api_key: \${{ secrets.${SECRET_NAME3} }}`,
|
|
933
|
+
` gemini_cli_version: '${GEMINI_CLI_VERSION}'`,
|
|
934
|
+
" prompt: |",
|
|
935
|
+
indentBlock(composePrompt3(input), 6)
|
|
936
|
+
].join("\n")
|
|
937
|
+
]
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
var geminiProvider = {
|
|
941
|
+
id: "gemini",
|
|
942
|
+
workflowFileName: ".github/workflows/gemini-code-review.yml",
|
|
943
|
+
requiredSecretName: SECRET_NAME3,
|
|
944
|
+
buildWorkflowYaml: buildWorkflowYaml3
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
// ../template-engine/dist/review/gpt/gpt-provider.js
|
|
948
|
+
var SECRET_NAME4 = "OPENAI_API_KEY";
|
|
949
|
+
function renderWorkflowInstructions4(locale) {
|
|
950
|
+
const lines = locale === "en" ? [
|
|
951
|
+
"Review ONLY the changes introduced by this pull request. The base and head commits are already fetched under fixed ref names: run `git diff origin/pr-base...origin/pr-head` (and `git log origin/pr-base..origin/pr-head` for commit context) \u2014 this sandboxed step has no network access, so do not attempt to run `gh` or reach the GitHub API.",
|
|
952
|
+
"Write your entire response as a single message: report specific findings, then close with one overall summary.",
|
|
953
|
+
`Always include the literal marker ${ROUND_MARKER} in your message.`,
|
|
954
|
+
"If there are no findings that require action, write exactly one message saying so (and still include the marker).",
|
|
955
|
+
"Do not modify any code, and never approve or merge the pull request.",
|
|
956
|
+
"Write summaries and explanations in English; keep file names, function names, and identifiers verbatim."
|
|
957
|
+
] : [
|
|
958
|
+
"\uC774 pull request\uAC00 \uB3C4\uC785\uD55C \uBCC0\uACBD \uC0AC\uD56D\uB9CC \uB9AC\uBDF0\uD558\uC138\uC694. base/head \uCEE4\uBC0B\uC740 \uACE0\uC815 ref \uC774\uB984\uC73C\uB85C \uC774\uBBF8 fetch\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: `git diff origin/pr-base...origin/pr-head`\uB85C \uBE44\uAD50\uD558\uACE0, \uCEE4\uBC0B \uB9E5\uB77D\uC740 `git log origin/pr-base..origin/pr-head`\uB85C \uD655\uC778\uD558\uC138\uC694(\uC774 \uC0CC\uB4DC\uBC15\uC2A4 \uC2A4\uD15D\uC740 \uB124\uD2B8\uC6CC\uD06C\uAC00 \uC5C6\uC73C\uBBC0\uB85C `gh`\uB098 GitHub API\uB97C \uD638\uCD9C\uD558\uC9C0 \uB9C8\uC138\uC694).",
|
|
959
|
+
"\uC804\uCCB4 \uC751\uB2F5\uC744 \uD558\uB098\uC758 \uBA54\uC2DC\uC9C0\uB85C \uC791\uC131\uD558\uC138\uC694: \uAD6C\uCCB4\uC801 \uC9C0\uC801\uC744 \uC801\uC740 \uB4A4 \uB9C8\uC9C0\uB9C9\uC5D0 \uC804\uCCB4 \uC694\uC57D\uC744 \uD55C \uBC88 \uB354 \uB0A8\uAE30\uC138\uC694.",
|
|
960
|
+
`\uBA54\uC2DC\uC9C0\uC5D0\uB294 \uBC18\uB4DC\uC2DC \uB9AC\uD130\uB7F4 \uB9C8\uCEE4 ${ROUND_MARKER}\uB97C \uD3EC\uD568\uD558\uC138\uC694.`,
|
|
961
|
+
'\uC870\uCE58\uAC00 \uD544\uC694\uD55C \uBB38\uC81C\uAC00 \uC5C6\uC73C\uBA74 "\uC870\uCE58\uAC00 \uD544\uC694\uD55C \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."\uB77C\uB294 \uBA54\uC2DC\uC9C0\uB97C \uC815\uD655\uD788 \uD55C \uAC1C \uC791\uC131\uD558\uC138\uC694(\uC774\uB54C\uB3C4 \uB9C8\uCEE4\uB294 \uD3EC\uD568).',
|
|
962
|
+
"\uCF54\uB4DC\uB97C \uC218\uC815\uD558\uC9C0 \uB9C8\uC138\uC694(Do not modify any code). PR\uC744 \uC2B9\uC778\uD558\uAC70\uB098 \uBCD1\uD569\uD558\uC9C0 \uB9C8\uC138\uC694(never approve or merge).",
|
|
963
|
+
"\uC694\uC57D\uACFC \uC124\uBA85\uC740 \uD55C\uAD6D\uC5B4\uB85C \uC4F0\uB418, \uD30C\uC77C\uBA85\xB7\uD568\uC218\uBA85\xB7\uC2DD\uBCC4\uC790\uB294 \uC6D0\uBB38 \uADF8\uB300\uB85C \uC720\uC9C0\uD558\uC138\uC694."
|
|
964
|
+
];
|
|
965
|
+
return lines.join("\n");
|
|
966
|
+
}
|
|
967
|
+
function composePrompt4(input) {
|
|
968
|
+
const mode = REVIEW_MODES[input.mode];
|
|
969
|
+
const sections = [renderWorkflowInstructions4(input.locale), buildCorrectnessSecurityPrompt(input.locale, mode.promptStyle)];
|
|
970
|
+
if (mode.includesOverEngineering) {
|
|
971
|
+
sections.push(buildOverEngineeringPrompt(input.locale));
|
|
972
|
+
}
|
|
973
|
+
sections.push(renderFindingsBlockInstruction(input.locale));
|
|
974
|
+
return sections.join("\n\n---\n\n");
|
|
975
|
+
}
|
|
976
|
+
function buildWorkflowYaml4(input) {
|
|
977
|
+
return renderReviewWorkflow(input, {
|
|
978
|
+
name: "GPT Code Review",
|
|
979
|
+
secretName: SECRET_NAME4,
|
|
980
|
+
// An API key: codex-action needs no OIDC token.
|
|
981
|
+
idToken: false,
|
|
982
|
+
reviewSteps: [
|
|
983
|
+
renderCheckoutStep(REVIEW_GATE_CONDITION, ["ref: refs/pull/${{ github.event.pull_request.number }}/merge"]),
|
|
984
|
+
[
|
|
985
|
+
"- name: Fetch base and head refs for the PR",
|
|
986
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
987
|
+
" env:",
|
|
988
|
+
" PR_BASE_REF: ${{ github.event.pull_request.base.ref }}",
|
|
989
|
+
" PR_NUMBER: ${{ github.event.pull_request.number }}",
|
|
990
|
+
" run: |",
|
|
991
|
+
" # Explicit destinations: the sandboxed review step has no network, so both sides",
|
|
992
|
+
" # of the diff must survive as stable local refs (FETCH_HEAD alone is ambiguous).",
|
|
993
|
+
" git fetch --no-tags origin \\",
|
|
994
|
+
' "+refs/heads/$PR_BASE_REF:refs/remotes/origin/pr-base" \\',
|
|
995
|
+
' "+refs/pull/$PR_NUMBER/head:refs/remotes/origin/pr-head"'
|
|
996
|
+
].join("\n"),
|
|
997
|
+
[
|
|
998
|
+
"- name: Run Codex review",
|
|
999
|
+
" id: codex-review",
|
|
1000
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
1001
|
+
` uses: ${pinnedUses("openai/codex-action")}`,
|
|
1002
|
+
" with:",
|
|
1003
|
+
` openai-api-key: \${{ secrets.${SECRET_NAME4} }}`,
|
|
1004
|
+
' permission-profile: ":read-only"',
|
|
1005
|
+
" prompt: |",
|
|
1006
|
+
indentBlock(composePrompt4(input), 6)
|
|
1007
|
+
].join("\n"),
|
|
1008
|
+
[
|
|
1009
|
+
"- name: Post review comment",
|
|
1010
|
+
` if: ${REVIEW_GATE_CONDITION}`,
|
|
1011
|
+
" env:",
|
|
1012
|
+
" GH_TOKEN: ${{ github.token }}",
|
|
1013
|
+
" PR_NUMBER: ${{ github.event.pull_request.number }}",
|
|
1014
|
+
" REVIEW_BODY: ${{ steps.codex-review.outputs.final-message }}",
|
|
1015
|
+
" run: |",
|
|
1016
|
+
' printf %s "$REVIEW_BODY" | gh pr comment "$PR_NUMBER" --body-file -'
|
|
1017
|
+
].join("\n")
|
|
1018
|
+
]
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
var gptProvider = {
|
|
1022
|
+
id: "gpt",
|
|
1023
|
+
workflowFileName: ".github/workflows/gpt-code-review.yml",
|
|
1024
|
+
requiredSecretName: SECRET_NAME4,
|
|
1025
|
+
buildWorkflowYaml: buildWorkflowYaml4
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// ../template-engine/dist/review/provider.js
|
|
1029
|
+
var REVIEW_PROVIDERS = {
|
|
1030
|
+
claude: claudeProvider,
|
|
1031
|
+
gemini: geminiProvider,
|
|
1032
|
+
gpt: gptProvider,
|
|
1033
|
+
cursor: cursorProvider
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
// ../template-engine/dist/review/compose-review-setup.js
|
|
1037
|
+
function composeReviewSetup(input) {
|
|
1038
|
+
if (!input.enabled)
|
|
1039
|
+
return null;
|
|
1040
|
+
const provider = REVIEW_PROVIDERS[input.provider];
|
|
1041
|
+
return {
|
|
1042
|
+
workflowFilePath: provider.workflowFileName,
|
|
1043
|
+
workflowYaml: provider.buildWorkflowYaml(input),
|
|
1044
|
+
requiredSecretName: provider.requiredSecretName,
|
|
1045
|
+
configSnippet: buildReviewConfigSnippet(input)
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// ../template-engine/dist/review/conflict.js
|
|
1050
|
+
function judgeReviewFileConflict(existing, generated) {
|
|
1051
|
+
if (existing === null)
|
|
1052
|
+
return { kind: "create" };
|
|
1053
|
+
if (existing === generated)
|
|
1054
|
+
return { kind: "unchanged" };
|
|
1055
|
+
return { kind: "merge-proposal", existing, generated };
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
export {
|
|
1059
|
+
DEFAULT_MAX_ROUNDS,
|
|
1060
|
+
REVIEW_MODES,
|
|
1061
|
+
VERDICT_MARKER,
|
|
1062
|
+
ROUND_MARKER,
|
|
1063
|
+
runVerdictGate,
|
|
1064
|
+
buildReviewConfigSnippet,
|
|
1065
|
+
REVIEW_PROVIDERS,
|
|
1066
|
+
composeReviewSetup,
|
|
1067
|
+
judgeReviewFileConflict
|
|
1068
|
+
};
|