@clearlydev/nx-archweave 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/bin/review-verdict.js +37 -11
- package/dist/generators/add-agent-rules/generator.js +76 -931
- package/dist/generators/add-backend/generator.js +6 -931
- package/dist/generators/add-fsd-lite/generator.js +4 -929
- package/dist/generators/add-review/generator.js +130 -944
- package/dist/generators/doctor/generator.js +247 -36
- package/dist/generators/init/generator.js +188 -951
- package/package.json +4 -6
- package/src/generators/init/schema.json +6 -0
package/README.md
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Nx generators that get a workspace ready to work on with one preset:
|
|
4
4
|
|
|
5
|
-
- FSD Lite
|
|
5
|
+
- an FSD Lite lint rule for the frontend app's layers (`warn` or `error` by preset), plus slice generators
|
|
6
|
+
- frontend/backend boundaries as lint errors between the projects it tags, when there is a backend
|
|
6
7
|
- AI agent rules
|
|
7
8
|
- GitHub templates
|
|
8
|
-
- optional AI PR review (Claude
|
|
9
|
+
- optional AI PR review with the provider you choose (Claude supported; Gemini, GPT and Cursor in beta)
|
|
9
10
|
|
|
10
11
|
Starting a brand-new workspace instead? Use [`create-nx-archweave`](https://www.npmjs.com/package/create-nx-archweave). It creates the workspace, installs this plugin, and runs `init`, all in one command.
|
|
11
12
|
|
|
@@ -32,7 +33,7 @@ bun nx g @clearlydev/nx-archweave:doctor # bun
|
|
|
32
33
|
- **Requirements:** Node >= 22.18, plus one of npm, pnpm >= 10, yarn (classic or berry) or bun. All four package managers are first-class.
|
|
33
34
|
- Generators detect the workspace's real package manager from its lockfiles.
|
|
34
35
|
- The examples below use `pnpm nx g`. With another package manager, run the same generator the way the `doctor` commands above show.
|
|
35
|
-
- The package ships as a
|
|
36
|
+
- The package ships its own code as a bundle. Its runtime dependencies are `@nx/devkit` and `jsonc-parser`, which nx already depends on at the same version.
|
|
36
37
|
|
|
37
38
|
## Quick start
|
|
38
39
|
|
|
@@ -213,9 +214,11 @@ Review is fully optional and isn't tied to one provider.
|
|
|
213
214
|
| `--provider` | Workflow file | Required secret |
|
|
214
215
|
| --- | --- | --- |
|
|
215
216
|
| `claude` (default) | `.github/workflows/claude-code-review.yml` | `CLAUDE_CODE_OAUTH_TOKEN` |
|
|
216
|
-
| `gemini` | `.github/workflows/gemini-code-review.yml` | `GEMINI_API_KEY` |
|
|
217
|
-
| `gpt` | `.github/workflows/gpt-code-review.yml` | `OPENAI_API_KEY` |
|
|
218
|
-
| `cursor` | `.github/workflows/cursor-code-review.yml` | `CURSOR_API_KEY` |
|
|
217
|
+
| `gemini` (beta) | `.github/workflows/gemini-code-review.yml` | `GEMINI_API_KEY` |
|
|
218
|
+
| `gpt` (beta) | `.github/workflows/gpt-code-review.yml` | `OPENAI_API_KEY` |
|
|
219
|
+
| `cursor` (beta) | `.github/workflows/cursor-code-review.yml` | `CURSOR_API_KEY` |
|
|
220
|
+
|
|
221
|
+
What each provider has been checked on, where to get its secret, and known issues: [Provider status](https://github.com/clearlydev/nx-archweave/blob/main/docs/pr-review.md#provider-status).
|
|
219
222
|
|
|
220
223
|
Modes:
|
|
221
224
|
|
|
@@ -150,15 +150,29 @@ var NO_ROUND_NOTICE = {
|
|
|
150
150
|
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.",
|
|
151
151
|
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."
|
|
152
152
|
};
|
|
153
|
+
var NO_SUMMARY_NOTICE = {
|
|
154
|
+
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.",
|
|
155
|
+
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."
|
|
156
|
+
};
|
|
157
|
+
var UNREADABLE_SUMMARY_NOTICE = {
|
|
158
|
+
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.",
|
|
159
|
+
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."
|
|
160
|
+
};
|
|
161
|
+
function renderCommitLine(locale, shortSha) {
|
|
162
|
+
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>`;
|
|
163
|
+
}
|
|
153
164
|
var HEAD_LINE = {
|
|
154
|
-
en: (sha) =>
|
|
155
|
-
ko: (sha) =>
|
|
165
|
+
en: (sha) => renderCommitLine("en", sha.slice(0, 7)),
|
|
166
|
+
ko: (sha) => renderCommitLine("ko", sha.slice(0, 7))
|
|
156
167
|
};
|
|
157
168
|
function withHead(lines, locale, headSha) {
|
|
158
169
|
return (headSha === void 0 ? lines : [...lines, "", HEAD_LINE[locale](headSha)]).join("\n");
|
|
159
170
|
}
|
|
160
|
-
function renderUnparseableNotice(locale, headSha) {
|
|
161
|
-
return withHead([VERDICT_MARKER, UNPARSEABLE_NOTICE[locale]], locale, headSha);
|
|
171
|
+
function renderUnparseableNotice(locale, headSha, failClosed = false) {
|
|
172
|
+
return withHead([VERDICT_MARKER, (failClosed ? UNREADABLE_SUMMARY_NOTICE : UNPARSEABLE_NOTICE)[locale]], locale, headSha);
|
|
173
|
+
}
|
|
174
|
+
function renderNoSummaryNotice(locale, headSha) {
|
|
175
|
+
return withHead([VERDICT_MARKER, NO_SUMMARY_NOTICE[locale]], locale, headSha);
|
|
162
176
|
}
|
|
163
177
|
function renderNoRoundNotice(locale, headSha) {
|
|
164
178
|
return withHead([VERDICT_MARKER, NO_ROUND_NOTICE[locale]], locale, headSha);
|
|
@@ -206,14 +220,16 @@ function computeRoundHistory(rounds) {
|
|
|
206
220
|
}
|
|
207
221
|
function runVerdictGate(input) {
|
|
208
222
|
const rounds = collectRounds(input.comments);
|
|
209
|
-
|
|
210
|
-
|
|
223
|
+
const reviewRan = input.reviewRan === true;
|
|
224
|
+
if (rounds.length === 0) {
|
|
225
|
+
return reviewRan ? { kind: "no-summary", commentBody: renderNoSummaryNotice(input.locale, input.headSha) } : { kind: "no-review" };
|
|
226
|
+
}
|
|
211
227
|
const latest = rounds[rounds.length - 1];
|
|
212
228
|
if (input.roundAfterId !== void 0 && (latest.id === void 0 || latest.id <= input.roundAfterId)) {
|
|
213
|
-
return { kind: "no-round", commentBody: renderNoRoundNotice(input.locale, input.headSha) };
|
|
229
|
+
return reviewRan ? { kind: "no-summary", commentBody: renderNoSummaryNotice(input.locale, input.headSha) } : { kind: "no-round", commentBody: renderNoRoundNotice(input.locale, input.headSha) };
|
|
214
230
|
}
|
|
215
231
|
if (latest.extraction.status !== "ok") {
|
|
216
|
-
return { kind: "unparseable", commentBody: renderUnparseableNotice(input.locale, input.headSha) };
|
|
232
|
+
return { kind: "unparseable", commentBody: renderUnparseableNotice(input.locale, input.headSha, reviewRan), failClosed: reviewRan };
|
|
217
233
|
}
|
|
218
234
|
const verdict = computeVerdict(latest.extraction.findings);
|
|
219
235
|
const commentBody = renderVerdictComment({
|
|
@@ -271,15 +287,18 @@ function runReviewVerdictBin(env, exec, writeOutput = realWriteOutput) {
|
|
|
271
287
|
const comments = pages.flatMap((page) => Array.isArray(page) ? page : []).filter(
|
|
272
288
|
(c) => c !== null && typeof c === "object" && typeof c.body === "string" && c.user?.type === "Bot"
|
|
273
289
|
).map((c) => ({ body: c.body, id: typeof c.id === "number" ? c.id : void 0 }));
|
|
274
|
-
const
|
|
290
|
+
const reviewRan = outputPath !== void 0 && roundAfterId !== void 0;
|
|
291
|
+
const result = runVerdictGate({ comments, maxRounds, locale, roundAfterId, headSha, reviewRan });
|
|
275
292
|
if (result.kind === "no-review") {
|
|
276
293
|
logs.push("verdict gate: no review rounds found on this PR \u2014 nothing to judge.");
|
|
277
294
|
return { exitCode: 0, logs };
|
|
278
295
|
}
|
|
279
296
|
const previous = [...comments].reverse().find((c) => c.body.includes(VERDICT_MARKER) && c.id !== void 0);
|
|
280
|
-
const
|
|
297
|
+
const failedReview = result.kind === "no-summary" || result.kind === "unparseable" && result.failClosed;
|
|
298
|
+
const settled = !failedReview && (result.kind === "no-round" || result.kind === "unparseable");
|
|
299
|
+
const blocked = failedReview || result.kind === "verdict" && result.exitCode !== 0;
|
|
281
300
|
if (outputPath !== void 0) {
|
|
282
|
-
writeOutput(outputPath, renderStepOutputs(result.commentBody, previous?.id,
|
|
301
|
+
writeOutput(outputPath, renderStepOutputs(result.commentBody, previous?.id, blocked));
|
|
283
302
|
} else {
|
|
284
303
|
const payload = JSON.stringify({ body: result.commentBody });
|
|
285
304
|
const updated = previous !== void 0 && exec("gh", ["api", "-X", "PATCH", `repos/${repo}/issues/comments/${previous.id}`, "--input", "-"], payload).status === 0;
|
|
@@ -288,12 +307,19 @@ function runReviewVerdictBin(env, exec, writeOutput = realWriteOutput) {
|
|
|
288
307
|
if (post.status !== 0) logs.push("verdict gate: could not post the verdict comment (continuing).");
|
|
289
308
|
}
|
|
290
309
|
}
|
|
310
|
+
if (failedReview) {
|
|
311
|
+
logs.push(
|
|
312
|
+
result.kind === "no-summary" ? "::error title=AI review posted no summary::The AI review ran but posted no summary for this commit, so there is no verdict. Review the PR manually, or re-run all jobs." : "::error title=AI review summary unreadable::The AI review summary has no readable findings block, so there is no verdict. Review the PR manually, or re-run all jobs."
|
|
313
|
+
);
|
|
314
|
+
return { exitCode: 0, logs };
|
|
315
|
+
}
|
|
291
316
|
if (settled) {
|
|
292
317
|
logs.push(
|
|
293
318
|
result.kind === "no-round" ? "verdict gate: this run posted no review round for the current commit \u2014 earlier rounds are not judged again; check stays green." : "verdict gate: review output was not machine-readable \u2014 human review requested, check stays green."
|
|
294
319
|
);
|
|
295
320
|
return { exitCode: 0, logs };
|
|
296
321
|
}
|
|
322
|
+
if (result.kind !== "verdict") return { exitCode: 0, logs };
|
|
297
323
|
logs.push(`verdict gate: ${result.verdict}`);
|
|
298
324
|
return { exitCode: outputPath === void 0 ? result.exitCode : 0, logs };
|
|
299
325
|
}
|