@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
|
@@ -288,6 +288,30 @@ function buildOverEngineeringPrompt(locale) {
|
|
|
288
288
|
return [...body, "", renderFindingSchemaInstruction(locale, OVER_ENGINEERING_PRIORITIES), "", renderDedupInstruction(locale)].join("\n");
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
// ../review-policy/dist/verdict-gate/verdict-comment.js
|
|
292
|
+
var VERDICT_MARKER = "<!-- archweave-verdict -->";
|
|
293
|
+
function renderCommitLine(locale, shortSha) {
|
|
294
|
+
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>`;
|
|
295
|
+
}
|
|
296
|
+
var NOT_RUN_REASON = {
|
|
297
|
+
en: {
|
|
298
|
+
secret: (secretName) => `the ${secretName} secret is not registered`,
|
|
299
|
+
fork: () => "this pull request comes from a fork, and GitHub passes no secrets to runs from forks",
|
|
300
|
+
dependabot: (secretName) => `Dependabot runs receive only Dependabot secrets, and ${secretName} is not one of them`,
|
|
301
|
+
rounds: (_secretName, maxRounds) => `the automated review round limit (${maxRounds}) is reached`
|
|
302
|
+
},
|
|
303
|
+
ko: {
|
|
304
|
+
secret: (secretName) => `${secretName} \uC2DC\uD06C\uB9BF\uC774 \uB4F1\uB85D\uB418\uC9C0 \uC54A\uC74C`,
|
|
305
|
+
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",
|
|
306
|
+
dependabot: (secretName) => `Dependabot \uC2E4\uD589\uC740 Dependabot \uC2DC\uD06C\uB9BF\uB9CC \uBC1B\uB294\uB370, ${secretName}\uB294 \uADF8\uC911\uC5D0 \uC5C6\uC74C`,
|
|
307
|
+
rounds: (_secretName, maxRounds) => `\uC790\uB3D9 \uB9AC\uBDF0 \uB77C\uC6B4\uB4DC \uD55C\uB3C4(${maxRounds})\uC5D0 \uB3C4\uB2EC\uD568`
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
function renderNotRunNotice(locale, reason, secretName, maxRounds) {
|
|
311
|
+
const why = NOT_RUN_REASON[locale][reason](secretName, maxRounds);
|
|
312
|
+
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.`;
|
|
313
|
+
}
|
|
314
|
+
|
|
291
315
|
// ../review-policy/dist/verdict-gate/verdict-gate.js
|
|
292
316
|
var ROUND_MARKER = "<!-- archweave-review-round -->";
|
|
293
317
|
|
|
@@ -369,8 +393,35 @@ function renderRoundGateStep(maxRounds) {
|
|
|
369
393
|
" fi"
|
|
370
394
|
].join("\n");
|
|
371
395
|
}
|
|
396
|
+
function shellQuote(text) {
|
|
397
|
+
return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
398
|
+
}
|
|
399
|
+
function renderNotRunFunction(secretName, maxRounds, locale) {
|
|
400
|
+
const reasons = ["secret", "fork", "dependabot", "rounds"];
|
|
401
|
+
const [commitBefore, commitAfter] = renderCommitLine(locale, "\0").split("\0");
|
|
402
|
+
return [
|
|
403
|
+
"not_run() {",
|
|
404
|
+
' case "$1" in',
|
|
405
|
+
...reasons.map((reason) => ` ${reason}) annotation=${shellQuote(renderNotRunNotice("en", reason, secretName, maxRounds))}; notice=${shellQuote(renderNotRunNotice(locale, reason, secretName, maxRounds))} ;;`),
|
|
406
|
+
" esac",
|
|
407
|
+
' echo "::notice title=AI review did not run::$annotation"',
|
|
408
|
+
` printf '%s\\n' "$notice" >> "$GITHUB_STEP_SUMMARY"`,
|
|
409
|
+
` short_sha=$(printf '%s' "$HEAD_SHA" | cut -c1-7)`,
|
|
410
|
+
' case "$short_sha" in',
|
|
411
|
+
" '' | *[!0-9a-f]*) short_sha= ;;",
|
|
412
|
+
" esac",
|
|
413
|
+
" {",
|
|
414
|
+
' echo "notice-body<<ARCHWEAVE_NOT_RUN_EOF"',
|
|
415
|
+
` printf '%s\\n' ${shellQuote(VERDICT_MARKER)} "\u2139\uFE0F $notice"`,
|
|
416
|
+
` if [ -n "$short_sha" ]; then printf '\\n%s\\n' ${shellQuote(commitBefore)}"$short_sha"${shellQuote(commitAfter)}; fi`,
|
|
417
|
+
' echo "ARCHWEAVE_NOT_RUN_EOF"',
|
|
418
|
+
' echo "run=false"',
|
|
419
|
+
' } >> "$GITHUB_OUTPUT"',
|
|
420
|
+
"}"
|
|
421
|
+
];
|
|
422
|
+
}
|
|
372
423
|
var JUDGE_CONDITION = "steps.ai-review-result.outputs.run == 'true'";
|
|
373
|
-
function renderAiReviewResultStep() {
|
|
424
|
+
function renderAiReviewResultStep(secretName, maxRounds, locale) {
|
|
374
425
|
return [
|
|
375
426
|
"- name: Check the AI review result",
|
|
376
427
|
" id: ai-review-result",
|
|
@@ -379,15 +430,26 @@ function renderAiReviewResultStep() {
|
|
|
379
430
|
" REVIEW_ENABLED: ${{ needs.ai-review.outputs.enabled }}",
|
|
380
431
|
" REVIEW_ALLOWED: ${{ needs.ai-review.outputs.allowed }}",
|
|
381
432
|
" LAST_COMMENT_ID: ${{ needs.ai-review.outputs.last-comment-id }}",
|
|
433
|
+
" HEAD_SHA: ${{ github.event.pull_request.head.sha }}",
|
|
434
|
+
" HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}",
|
|
435
|
+
" REPOSITORY: ${{ github.repository }}",
|
|
436
|
+
" PR_AUTHOR: ${{ github.event.pull_request.user.login }}",
|
|
382
437
|
" run: |",
|
|
438
|
+
...renderNotRunFunction(secretName, maxRounds, locale).map((line) => ` ${line}`),
|
|
383
439
|
' if [ "$AI_REVIEW_RESULT" != success ]; then',
|
|
384
440
|
` echo "::error title=AI review did not succeed::The AI review job ended with '$AI_REVIEW_RESULT'."`,
|
|
385
441
|
" exit 1",
|
|
386
442
|
" fi",
|
|
387
443
|
' case "$REVIEW_ENABLED/$REVIEW_ALLOWED" in',
|
|
388
|
-
" false/*
|
|
389
|
-
'
|
|
390
|
-
|
|
444
|
+
" false/*)",
|
|
445
|
+
' if [ "$HEAD_REPOSITORY" != "$REPOSITORY" ]; then not_run fork',
|
|
446
|
+
` elif [ "$PR_AUTHOR" = 'dependabot[bot]' ]; then not_run dependabot`,
|
|
447
|
+
" else not_run secret",
|
|
448
|
+
" fi",
|
|
449
|
+
" exit 0",
|
|
450
|
+
" ;;",
|
|
451
|
+
" true/false)",
|
|
452
|
+
" not_run rounds",
|
|
391
453
|
" exit 0",
|
|
392
454
|
" ;;",
|
|
393
455
|
" true/true)",
|
|
@@ -431,7 +493,7 @@ function renderBlockStep() {
|
|
|
431
493
|
"- name: Fail on a blocking verdict",
|
|
432
494
|
" if: steps.verdict.outputs.blocked == 'true'",
|
|
433
495
|
" run: |",
|
|
434
|
-
' echo "The review verdict is BLOCK
|
|
496
|
+
' echo "The review check failed: the verdict is BLOCK, or the AI review left no readable summary. See the verdict comment on this pull request."',
|
|
435
497
|
" exit 1"
|
|
436
498
|
].join("\n");
|
|
437
499
|
}
|
|
@@ -446,6 +508,9 @@ function renderVerdictCommentStep() {
|
|
|
446
508
|
" PREVIOUS_ID: ${{ needs.review.outputs.previous-comment-id }}",
|
|
447
509
|
" run: |",
|
|
448
510
|
` payload=$(jq -n --arg body "$VERDICT_BODY" '{body: $body}')`,
|
|
511
|
+
' if [ -z "$PREVIOUS_ID" ]; then',
|
|
512
|
+
` 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=`,
|
|
513
|
+
" fi",
|
|
449
514
|
' case "$PREVIOUS_ID" in',
|
|
450
515
|
" ''|*[!0-9]*) PREVIOUS_ID= ;;",
|
|
451
516
|
" esac",
|
|
@@ -516,10 +581,11 @@ function renderWorkspaceSetupSteps(packageManager) {
|
|
|
516
581
|
].join("\n"));
|
|
517
582
|
return steps.join("\n\n");
|
|
518
583
|
}
|
|
584
|
+
var REVIEW_JOB_TIMEOUT_MINUTES = 15;
|
|
519
585
|
function renderReviewWorkflow(input, parts) {
|
|
520
586
|
const aiReviewSteps = [renderSecretGateStep(parts.secretName), renderRoundGateStep(input.maxRounds), ...parts.reviewSteps];
|
|
521
587
|
const reviewSteps = [
|
|
522
|
-
renderAiReviewResultStep(),
|
|
588
|
+
renderAiReviewResultStep(parts.secretName, input.maxRounds, input.locale),
|
|
523
589
|
renderCheckoutStep(JUDGE_CONDITION),
|
|
524
590
|
renderWorkspaceSetupSteps(input.packageManager),
|
|
525
591
|
renderVerdictGateStep(input.maxRounds, input.locale),
|
|
@@ -537,6 +603,7 @@ function renderReviewWorkflow(input, parts) {
|
|
|
537
603
|
"jobs:",
|
|
538
604
|
" ai-review:",
|
|
539
605
|
" runs-on: ubuntu-latest",
|
|
606
|
+
` timeout-minutes: ${REVIEW_JOB_TIMEOUT_MINUTES}`,
|
|
540
607
|
indentBlock(renderReviewPermissions(parts.idToken), 4),
|
|
541
608
|
" outputs:",
|
|
542
609
|
" enabled: ${{ steps.secret-gate.outputs.enabled }}",
|
|
@@ -549,11 +616,12 @@ function renderReviewWorkflow(input, parts) {
|
|
|
549
616
|
" needs: ai-review",
|
|
550
617
|
" if: always()",
|
|
551
618
|
" runs-on: ubuntu-latest",
|
|
619
|
+
` timeout-minutes: ${REVIEW_JOB_TIMEOUT_MINUTES}`,
|
|
552
620
|
" permissions:",
|
|
553
621
|
" contents: read",
|
|
554
622
|
" pull-requests: read",
|
|
555
623
|
" outputs:",
|
|
556
|
-
" comment-body: ${{ steps.verdict.outputs.comment-body }}",
|
|
624
|
+
" comment-body: ${{ steps.verdict.outputs.comment-body || steps.ai-review-result.outputs.notice-body }}",
|
|
557
625
|
" previous-comment-id: ${{ steps.verdict.outputs.previous-comment-id }}",
|
|
558
626
|
" steps:",
|
|
559
627
|
indentBlock(reviewSteps.join("\n\n"), 6),
|
|
@@ -563,6 +631,7 @@ function renderReviewWorkflow(input, parts) {
|
|
|
563
631
|
// Runs after a BLOCK failed the review job, too: the comment explains the red check.
|
|
564
632
|
" if: ${{ !cancelled() && needs.review.outputs.comment-body != '' }}",
|
|
565
633
|
" runs-on: ubuntu-latest",
|
|
634
|
+
` timeout-minutes: ${REVIEW_JOB_TIMEOUT_MINUTES}`,
|
|
566
635
|
" permissions:",
|
|
567
636
|
" pull-requests: write",
|
|
568
637
|
" steps:",
|
|
@@ -574,17 +643,27 @@ function renderReviewWorkflow(input, parts) {
|
|
|
574
643
|
// ../template-engine/dist/review/claude/claude-provider.js
|
|
575
644
|
var SECRET_NAME = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
576
645
|
var ALLOWED_TOOLS = "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)";
|
|
646
|
+
var CLAUDE_STEP_ID = "claude-review";
|
|
647
|
+
var PR_CONTEXT = ["REPO: ${{ github.repository }}", "PR NUMBER: ${{ github.event.pull_request.number }}"];
|
|
577
648
|
function renderWorkflowInstructions(locale) {
|
|
578
649
|
const lines = locale === "en" ? [
|
|
579
|
-
|
|
580
|
-
"
|
|
650
|
+
...PR_CONTEXT,
|
|
651
|
+
"",
|
|
652
|
+
"Review the diff of this pull request using `gh pr diff <PR NUMBER>` and `gh pr view <PR NUMBER>`.",
|
|
653
|
+
"Post specific findings as inline comments with `mcp__github_inline_comment__create_inline_comment` (pass `confirmed: true`), and exactly one top-level summary comment.",
|
|
654
|
+
"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 `$(...)`.",
|
|
655
|
+
"Run each `gh` command on its own: no pipes to other programs, no `&&` chains.",
|
|
581
656
|
`Always include the literal marker ${ROUND_MARKER} in the top-level summary comment.`,
|
|
582
657
|
"If there are no findings that require action, post exactly one top-level comment saying so (and still include the marker).",
|
|
583
658
|
"Do not modify any code, and never approve or merge the pull request.",
|
|
584
659
|
"Write summaries and explanations in English; keep file names, function names, and identifiers verbatim."
|
|
585
660
|
] : [
|
|
586
|
-
|
|
587
|
-
"
|
|
661
|
+
...PR_CONTEXT,
|
|
662
|
+
"",
|
|
663
|
+
"\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.",
|
|
664
|
+
"\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.",
|
|
665
|
+
"\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.",
|
|
666
|
+
"`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.",
|
|
588
667
|
`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.`,
|
|
589
668
|
'\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).',
|
|
590
669
|
"\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).",
|
|
@@ -601,6 +680,20 @@ function composePrompt(input) {
|
|
|
601
680
|
sections.push(renderFindingsBlockInstruction(input.locale));
|
|
602
681
|
return sections.join("\n\n---\n\n");
|
|
603
682
|
}
|
|
683
|
+
function renderDeniedToolsStep() {
|
|
684
|
+
return [
|
|
685
|
+
"- name: Show denied tool calls",
|
|
686
|
+
` if: always() && steps.${CLAUDE_STEP_ID}.outputs.execution_file != ''`,
|
|
687
|
+
" env:",
|
|
688
|
+
` EXECUTION_FILE: \${{ steps.${CLAUDE_STEP_ID}.outputs.execution_file }}`,
|
|
689
|
+
" run: |",
|
|
690
|
+
' [ -f "$EXECUTION_FILE" ] || exit 0',
|
|
691
|
+
` stop_token="archweave-$(od -An -N16 -tx1 /dev/urandom | tr -d ' \\n')"`,
|
|
692
|
+
' echo "::stop-commands::$stop_token"',
|
|
693
|
+
` jq -c '.[] | select(.type == "result") | .permission_denials[]? | {tool: .tool_name, command: ((.tool_input.command // "") | tostring | .[0:200])}' "$EXECUTION_FILE" || echo "Could not read the execution file."`,
|
|
694
|
+
' echo "::$stop_token::"'
|
|
695
|
+
].join("\n");
|
|
696
|
+
}
|
|
604
697
|
function buildWorkflowYaml(input) {
|
|
605
698
|
return renderReviewWorkflow(input, {
|
|
606
699
|
name: "Claude Code Review",
|
|
@@ -611,6 +704,7 @@ function buildWorkflowYaml(input) {
|
|
|
611
704
|
renderCheckoutStep(REVIEW_GATE_CONDITION),
|
|
612
705
|
[
|
|
613
706
|
"- name: Run Claude review",
|
|
707
|
+
` id: ${CLAUDE_STEP_ID}`,
|
|
614
708
|
` if: ${REVIEW_GATE_CONDITION}`,
|
|
615
709
|
` uses: ${pinnedUses("anthropics/claude-code-action")}`,
|
|
616
710
|
" with:",
|
|
@@ -619,7 +713,8 @@ function buildWorkflowYaml(input) {
|
|
|
619
713
|
indentBlock(composePrompt(input), 6),
|
|
620
714
|
" claude_args: |",
|
|
621
715
|
` --allowedTools "${ALLOWED_TOOLS}"`
|
|
622
|
-
].join("\n")
|
|
716
|
+
].join("\n"),
|
|
717
|
+
renderDeniedToolsStep()
|
|
623
718
|
]
|
|
624
719
|
});
|
|
625
720
|
}
|
|
@@ -687,7 +782,9 @@ function buildWorkflowYaml2(input) {
|
|
|
687
782
|
` CURSOR_API_KEY: \${{ secrets.${SECRET_NAME2} }}`,
|
|
688
783
|
" GH_TOKEN: ${{ github.token }}",
|
|
689
784
|
" run: |",
|
|
690
|
-
|
|
785
|
+
// cursor-agent stops at "Workspace Trust Required" without --trust (2026.10.22 on
|
|
786
|
+
// Actions); the folder is this run's own checkout, and forks never get the secret.
|
|
787
|
+
` agent --trust -p "$(cat <<'${PROMPT_HEREDOC_DELIMITER}'`,
|
|
691
788
|
indentBlock(composePrompt2(input), 4),
|
|
692
789
|
` ${PROMPT_HEREDOC_DELIMITER}`,
|
|
693
790
|
' )"'
|
|
@@ -731,6 +828,7 @@ function composePrompt3(input) {
|
|
|
731
828
|
sections.push(renderFindingsBlockInstruction(input.locale));
|
|
732
829
|
return sections.join("\n\n---\n\n");
|
|
733
830
|
}
|
|
831
|
+
var GEMINI_CLI_VERSION = "0.65.0";
|
|
734
832
|
function buildWorkflowYaml3(input) {
|
|
735
833
|
return renderReviewWorkflow(input, {
|
|
736
834
|
name: "Gemini Code Review",
|
|
@@ -745,8 +843,13 @@ function buildWorkflowYaml3(input) {
|
|
|
745
843
|
` uses: ${pinnedUses("google-github-actions/run-gemini-cli")}`,
|
|
746
844
|
" env:",
|
|
747
845
|
" GITHUB_TOKEN: ${{ github.token }}",
|
|
846
|
+
// Gemini CLI 0.65 refuses to run in a folder it hasn't been told to trust (measured
|
|
847
|
+
// 2026-10-25 on Actions). The folder is this run's own checkout; pull requests from forks
|
|
848
|
+
// never get here, because they get no secret.
|
|
849
|
+
" GEMINI_CLI_TRUST_WORKSPACE: 'true'",
|
|
748
850
|
" with:",
|
|
749
851
|
` gemini_api_key: \${{ secrets.${SECRET_NAME3} }}`,
|
|
852
|
+
` gemini_cli_version: '${GEMINI_CLI_VERSION}'`,
|
|
750
853
|
" prompt: |",
|
|
751
854
|
indentBlock(composePrompt3(input), 6)
|
|
752
855
|
].join("\n")
|
|
@@ -919,7 +1022,8 @@ var REVIEW_CHECK_IDS = [
|
|
|
919
1022
|
"review-alt-file-coexistence",
|
|
920
1023
|
"review-config-consistency",
|
|
921
1024
|
"review-workflow-providers",
|
|
922
|
-
"review-workflow-pnpm"
|
|
1025
|
+
"review-workflow-pnpm",
|
|
1026
|
+
"review-workflow-fail-open"
|
|
923
1027
|
];
|
|
924
1028
|
var WORKFLOWS_DIR = ".github/workflows";
|
|
925
1029
|
function archweaveWorkflows(root) {
|
|
@@ -976,6 +1080,25 @@ function buildReviewPnpmCheck(root, workflows) {
|
|
|
976
1080
|
suggestedAction: "Regenerate the workflow: nx g @clearlydev/nx-archweave:add-review --onConflict=overwrite."
|
|
977
1081
|
});
|
|
978
1082
|
}
|
|
1083
|
+
var FAIL_CLOSED_VERDICT_MODE = "ARCHWEAVE_VERDICT_OUTPUT: 'github-output'";
|
|
1084
|
+
function buildReviewFailOpenCheck(root, workflows) {
|
|
1085
|
+
const id = "review-workflow-fail-open";
|
|
1086
|
+
const label = "Review check fails closed";
|
|
1087
|
+
if (workflows.length === 0)
|
|
1088
|
+
return createDoctorCheck({ id, label, status: "not-applicable", message: "no nx-archweave review workflow", evidence: [] });
|
|
1089
|
+
const failOpen = workflows.filter((path) => !readFileSync2(join2(root, path), "utf-8").includes(FAIL_CLOSED_VERDICT_MODE));
|
|
1090
|
+
if (failOpen.length === 0) {
|
|
1091
|
+
return createDoctorCheck({ id, label, status: "pass", message: "a review run that leaves no readable summary fails the review check", evidence: workflows });
|
|
1092
|
+
}
|
|
1093
|
+
return createDoctorCheck({
|
|
1094
|
+
id,
|
|
1095
|
+
label,
|
|
1096
|
+
status: "error",
|
|
1097
|
+
message: "fail-open workflow: generated before nx-archweave 0.2.2, it passes the review check when the AI review posts no summary or one that cannot be read, so an unreviewed pull request can merge",
|
|
1098
|
+
evidence: failOpen,
|
|
1099
|
+
suggestedAction: "Re-run add-review: nx g @clearlydev/nx-archweave:add-review --onConflict=overwrite. The report shows the difference before anything is replaced."
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
979
1102
|
function buildReviewChecks(root) {
|
|
980
1103
|
const config = readToolkitConfigState(root);
|
|
981
1104
|
const mainWorkflowPath = REVIEW_PROVIDERS[resolveProviderId(config)].workflowFileName;
|
|
@@ -989,7 +1112,8 @@ function buildReviewChecks(root) {
|
|
|
989
1112
|
buildReviewAltFileCoexistenceCheck(mainWorkflowPath, altWorkflowPath, mainExists, altExists),
|
|
990
1113
|
buildReviewConfigConsistencyCheck(root, mainWorkflowPath, mainExists, config),
|
|
991
1114
|
buildReviewProvidersCheck(workflows),
|
|
992
|
-
buildReviewPnpmCheck(root, workflows)
|
|
1115
|
+
buildReviewPnpmCheck(root, workflows),
|
|
1116
|
+
buildReviewFailOpenCheck(root, workflows)
|
|
993
1117
|
];
|
|
994
1118
|
}
|
|
995
1119
|
function buildReviewWorkflowPresentCheck(mainWorkflowPath, mainExists, mainIsOurs, config) {
|
|
@@ -1228,15 +1352,22 @@ var BACKEND_CHECK_IDS = [
|
|
|
1228
1352
|
"backend-config-consistency",
|
|
1229
1353
|
"shared-types-lib",
|
|
1230
1354
|
"boundary-constraints-applied",
|
|
1231
|
-
"boundary-tags-exist"
|
|
1355
|
+
"boundary-tags-exist",
|
|
1356
|
+
"boundary-untagged-projects"
|
|
1232
1357
|
];
|
|
1233
1358
|
var CHECK_LABELS = {
|
|
1234
1359
|
"backend-framework": "Backend framework (root package.json only)",
|
|
1235
1360
|
"backend-config-consistency": "Backend config consistency",
|
|
1236
1361
|
"shared-types-lib": "shared-types library",
|
|
1237
1362
|
"boundary-constraints-applied": "Module-boundary constraints",
|
|
1238
|
-
"boundary-tags-exist": "Boundary tags exist"
|
|
1363
|
+
"boundary-tags-exist": "Boundary tags exist",
|
|
1364
|
+
"boundary-untagged-projects": "Projects without a boundary tag"
|
|
1239
1365
|
};
|
|
1366
|
+
var UNTAGGED_LISTED = 10;
|
|
1367
|
+
function isEndToEndRoot(projectRoot) {
|
|
1368
|
+
const base = projectRoot.split("/").pop() ?? "";
|
|
1369
|
+
return base === "e2e" || base.endsWith("-e2e");
|
|
1370
|
+
}
|
|
1240
1371
|
function collectScopeTags(root, projects) {
|
|
1241
1372
|
if (projects !== void 0)
|
|
1242
1373
|
return new Set(projects.flatMap((project) => project.tags));
|
|
@@ -1393,13 +1524,15 @@ function buildBackendChecks(root, projects) {
|
|
|
1393
1524
|
suggestedAction: classification === "enforced" ? void 0 : "Run add-backend with --applyEslint, or merge its snippet yourself."
|
|
1394
1525
|
}));
|
|
1395
1526
|
if (classification !== "enforced") {
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1527
|
+
for (const id of ["boundary-tags-exist", "boundary-untagged-projects"]) {
|
|
1528
|
+
checks.push(createDoctorCheck({
|
|
1529
|
+
id,
|
|
1530
|
+
label: CHECK_LABELS[id],
|
|
1531
|
+
status: "not-applicable",
|
|
1532
|
+
message: "Skipped \u2014 boundary constraints are not enforced (see boundary-constraints-applied)",
|
|
1533
|
+
evidence: []
|
|
1534
|
+
}));
|
|
1535
|
+
}
|
|
1403
1536
|
} else {
|
|
1404
1537
|
const tags = collectScopeTags(root, projects);
|
|
1405
1538
|
const required = readsBackendOnly(root) ? [boundaryTags.backend] : [boundaryTags.frontend, boundaryTags.backend];
|
|
@@ -1412,9 +1545,36 @@ function buildBackendChecks(root, projects) {
|
|
|
1412
1545
|
evidence: missing.length === 0 ? [] : missing,
|
|
1413
1546
|
suggestedAction: missing.length === 0 ? void 0 : "Add the missing tag(s) to the app project.json tags or package.json nx.tags (add-backend does this for the two apps)."
|
|
1414
1547
|
}));
|
|
1548
|
+
checks.push(buildUntaggedProjectsCheck(projects, [boundaryTags.frontend, boundaryTags.backend, boundaryTags.shared]));
|
|
1415
1549
|
}
|
|
1416
1550
|
return checks;
|
|
1417
1551
|
}
|
|
1552
|
+
function buildUntaggedProjectsCheck(projects, roleTags) {
|
|
1553
|
+
const id = "boundary-untagged-projects";
|
|
1554
|
+
if (projects === void 0) {
|
|
1555
|
+
return createDoctorCheck({
|
|
1556
|
+
id,
|
|
1557
|
+
label: CHECK_LABELS[id],
|
|
1558
|
+
status: "not-applicable",
|
|
1559
|
+
message: "Skipped \u2014 needs the workspace's Nx project list (run doctor through nx g)",
|
|
1560
|
+
evidence: []
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
const untagged = projects.filter((project) => project.root !== "" && project.root !== "." && !isEndToEndRoot(project.root)).filter((project) => !project.tags.some((tag) => roleTags.includes(tag))).map((project) => project.name);
|
|
1564
|
+
if (untagged.length === 0) {
|
|
1565
|
+
return createDoctorCheck({ id, label: CHECK_LABELS[id], status: "pass", message: `every project carries one of ${roleTags.join(", ")}`, evidence: [] });
|
|
1566
|
+
}
|
|
1567
|
+
const listed = untagged.slice(0, UNTAGGED_LISTED).join(", ");
|
|
1568
|
+
const more = untagged.length > UNTAGGED_LISTED ? ` and ${untagged.length - UNTAGGED_LISTED} more` : "";
|
|
1569
|
+
return createDoctorCheck({
|
|
1570
|
+
id,
|
|
1571
|
+
label: CHECK_LABELS[id],
|
|
1572
|
+
status: "warning",
|
|
1573
|
+
message: `${untagged.length} project(s) carry none of ${roleTags.join(", ")}, so the frontend/backend deny rules don't cover them: ${listed}${more}`,
|
|
1574
|
+
evidence: untagged,
|
|
1575
|
+
suggestedAction: `Give each one the tag of its side (${roleTags.join(", ")}) in package.json nx.tags or project.json tags. When you generate a library, pass it right away: nx g @nx/js:lib libs/<name> --tags=${roleTags[2]}.`
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1418
1578
|
|
|
1419
1579
|
// ../template-engine/dist/doctor/fsd-like-check.js
|
|
1420
1580
|
import { existsSync as existsSync4, readdirSync as readdirSync3 } from "node:fs";
|
|
@@ -1665,7 +1825,7 @@ var NOT_APPLICABLE_IDS = [
|
|
|
1665
1825
|
...REVIEW_CHECK_IDS,
|
|
1666
1826
|
...BACKEND_CHECK_IDS
|
|
1667
1827
|
];
|
|
1668
|
-
function runDoctorChecks(root, projects) {
|
|
1828
|
+
function runDoctorChecks(root, projects, nxSupport) {
|
|
1669
1829
|
const env = detectEnvironment(root);
|
|
1670
1830
|
if (!env.hasNxWorkspace) {
|
|
1671
1831
|
const checks2 = [
|
|
@@ -1695,7 +1855,7 @@ function runDoctorChecks(root, projects) {
|
|
|
1695
1855
|
message: "nx.json found",
|
|
1696
1856
|
evidence: ["nx.json"]
|
|
1697
1857
|
}),
|
|
1698
|
-
buildNxVersionCheck(env),
|
|
1858
|
+
buildNxVersionCheck(env, nxSupport),
|
|
1699
1859
|
buildFsdLikeCheck(root, projects),
|
|
1700
1860
|
buildFsdEnforcementCheck(root, projects),
|
|
1701
1861
|
buildFsdRuleAnchoredCheck(root, projects),
|
|
@@ -1724,12 +1884,25 @@ function runDoctorChecks(root, projects) {
|
|
|
1724
1884
|
const overallStatus = computeOverallStatus(checks);
|
|
1725
1885
|
return { checks, overallStatus };
|
|
1726
1886
|
}
|
|
1727
|
-
var
|
|
1887
|
+
var VALIDATED_NX_MAJORS = [23, 24];
|
|
1888
|
+
var VALIDATED_RANGE_TEXT = `${VALIDATED_NX_MAJORS[0]}.x\u2013${VALIDATED_NX_MAJORS[VALIDATED_NX_MAJORS.length - 1]}.x`;
|
|
1728
1889
|
function extractMajor(version) {
|
|
1729
1890
|
const match = /(\d+)/.exec(version);
|
|
1730
1891
|
return match ? Number(match[1]) : null;
|
|
1731
1892
|
}
|
|
1732
|
-
function buildNxVersionCheck(env) {
|
|
1893
|
+
function buildNxVersionCheck(env, nxSupport) {
|
|
1894
|
+
if (nxSupport?.inRange === false && nxSupport.installedVersion !== void 0) {
|
|
1895
|
+
const major2 = extractMajor(nxSupport.installedVersion);
|
|
1896
|
+
const supported = `${nxSupport.supportedMajors.min}.x\u2013${nxSupport.supportedMajors.max}.x`;
|
|
1897
|
+
return createDoctorCheck({
|
|
1898
|
+
id: "nx-version",
|
|
1899
|
+
label: "Nx version",
|
|
1900
|
+
status: "error",
|
|
1901
|
+
message: `Nx ${nxSupport.installedVersion} is installed, outside the majors the plugin's @nx/devkit ${nxSupport.devkitVersion} supports (nx ${supported}) \u2014 the generators may fail to load or misbehave`,
|
|
1902
|
+
evidence: [`nx@${nxSupport.installedVersion}`, `@nx/devkit@${nxSupport.devkitVersion}`],
|
|
1903
|
+
suggestedAction: `Install the @clearlydev/nx-archweave release built for Nx ${major2 ?? "this"}${major2 === null ? "" : ".x"}, or keep nx at ${supported}.`
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1733
1906
|
if (env.rootPackageJsonError) {
|
|
1734
1907
|
return createDoctorCheck({
|
|
1735
1908
|
id: "nx-version",
|
|
@@ -1759,30 +1932,30 @@ function buildNxVersionCheck(env) {
|
|
|
1759
1932
|
evidence: [`nx@${env.nxVersion}`]
|
|
1760
1933
|
});
|
|
1761
1934
|
}
|
|
1762
|
-
if (major
|
|
1935
|
+
if (VALIDATED_NX_MAJORS.includes(major)) {
|
|
1763
1936
|
return createDoctorCheck({
|
|
1764
1937
|
id: "nx-version",
|
|
1765
1938
|
label: "Nx version",
|
|
1766
1939
|
status: "pass",
|
|
1767
|
-
message: `Nx ${env.nxVersion} \u2014 within the toolkit-validated range (${
|
|
1940
|
+
message: `Nx ${env.nxVersion} \u2014 within the toolkit-validated range (${VALIDATED_RANGE_TEXT})`,
|
|
1768
1941
|
evidence: [`nx@${env.nxVersion}`]
|
|
1769
1942
|
});
|
|
1770
1943
|
}
|
|
1771
|
-
if (major <
|
|
1944
|
+
if (major < VALIDATED_NX_MAJORS[0]) {
|
|
1772
1945
|
return createDoctorCheck({
|
|
1773
1946
|
id: "nx-version",
|
|
1774
1947
|
label: "Nx version",
|
|
1775
1948
|
status: "warning",
|
|
1776
|
-
message: `Nx ${env.nxVersion} is below the toolkit-validated range (${
|
|
1949
|
+
message: `Nx ${env.nxVersion} is below the toolkit-validated range (${VALIDATED_RANGE_TEXT}) \u2014 generator behavior is not guaranteed`,
|
|
1777
1950
|
evidence: [`nx@${env.nxVersion}`],
|
|
1778
|
-
suggestedAction: `Upgrade the workspace to Nx ${
|
|
1951
|
+
suggestedAction: `Upgrade the workspace to Nx ${VALIDATED_NX_MAJORS[VALIDATED_NX_MAJORS.length - 1]}.x before running the generators.`
|
|
1779
1952
|
});
|
|
1780
1953
|
}
|
|
1781
1954
|
return createDoctorCheck({
|
|
1782
1955
|
id: "nx-version",
|
|
1783
1956
|
label: "Nx version",
|
|
1784
1957
|
status: "warning",
|
|
1785
|
-
message: `Nx ${env.nxVersion} is above the toolkit-validated range (${
|
|
1958
|
+
message: `Nx ${env.nxVersion} is above the toolkit-validated range (${VALIDATED_RANGE_TEXT}) \u2014 may work, unvalidated`,
|
|
1786
1959
|
evidence: [`nx@${env.nxVersion}`],
|
|
1787
1960
|
suggestedAction: "Proceed with care and report issues; validation for newer majors lands with toolkit updates."
|
|
1788
1961
|
});
|
|
@@ -2714,7 +2887,7 @@ function diskRunNx(root) {
|
|
|
2714
2887
|
var DOCTOR_TARGET_ENVIRONMENT = "claude-code";
|
|
2715
2888
|
async function buildDoctorReportWithIntegrations(root, options = {}, deps = {}) {
|
|
2716
2889
|
const integrationsRoot = deps.integrationsRoot ?? createIntegrationsCompositionRoot();
|
|
2717
|
-
const baseReport = runDoctorChecks(root, options.projects);
|
|
2890
|
+
const baseReport = runDoctorChecks(root, options.projects, options.nxSupport);
|
|
2718
2891
|
const declaredIds = readEnabledIntegrationIds(root);
|
|
2719
2892
|
const runNx = diskRunNx(root);
|
|
2720
2893
|
const driftResults = await detectIntegrationsDrift({
|
|
@@ -3163,11 +3336,49 @@ function buildPolicySuggestion(tree, sourceRoot, current) {
|
|
|
3163
3336
|
});
|
|
3164
3337
|
}
|
|
3165
3338
|
|
|
3339
|
+
// src/generators/doctor/nx-support.ts
|
|
3340
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
3341
|
+
import { createRequire } from "node:module";
|
|
3342
|
+
import { join as join12 } from "node:path";
|
|
3343
|
+
var MAJORS_EITHER_SIDE = 1;
|
|
3344
|
+
function majorOf(version) {
|
|
3345
|
+
const match = /^(\d+)\./.exec(version.trim());
|
|
3346
|
+
return match ? Number(match[1]) : void 0;
|
|
3347
|
+
}
|
|
3348
|
+
function readOwnDevkitVersion() {
|
|
3349
|
+
try {
|
|
3350
|
+
const path = createRequire(import.meta.url).resolve("@nx/devkit/package.json");
|
|
3351
|
+
const version = JSON.parse(readFileSync9(path, "utf-8")).version;
|
|
3352
|
+
return typeof version === "string" ? version : void 0;
|
|
3353
|
+
} catch {
|
|
3354
|
+
return void 0;
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
function readInstalledNx(root) {
|
|
3358
|
+
try {
|
|
3359
|
+
const version = JSON.parse(readFileSync9(join12(root, "node_modules", "nx", "package.json"), "utf-8")).version;
|
|
3360
|
+
return typeof version === "string" ? version : void 0;
|
|
3361
|
+
} catch {
|
|
3362
|
+
return void 0;
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
function readNxSupport(root, deps = {}) {
|
|
3366
|
+
const devkitVersion = (deps.devkitVersion ?? readOwnDevkitVersion)();
|
|
3367
|
+
const devkitMajor = devkitVersion === void 0 ? void 0 : majorOf(devkitVersion);
|
|
3368
|
+
if (devkitVersion === void 0 || devkitMajor === void 0) return void 0;
|
|
3369
|
+
const supportedMajors = { min: devkitMajor - MAJORS_EITHER_SIDE, max: devkitMajor + MAJORS_EITHER_SIDE };
|
|
3370
|
+
const installedVersion = (deps.installedNx ?? readInstalledNx)(root);
|
|
3371
|
+
const installedMajor = installedVersion === void 0 ? void 0 : majorOf(installedVersion);
|
|
3372
|
+
const inRange = installedMajor === void 0 ? void 0 : installedMajor >= supportedMajors.min && installedMajor <= supportedMajors.max;
|
|
3373
|
+
return { devkitVersion, supportedMajors, installedVersion, inRange };
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3166
3376
|
// src/generators/doctor/generator.ts
|
|
3167
3377
|
async function doctorGenerator(tree, options) {
|
|
3168
3378
|
const base = await buildDoctorReportWithIntegrations(tree.root, {
|
|
3169
3379
|
liveIntegrations: options.liveIntegrations ?? false,
|
|
3170
|
-
projects: doctorProjects(tree)
|
|
3380
|
+
projects: doctorProjects(tree),
|
|
3381
|
+
nxSupport: readNxSupport(tree.root)
|
|
3171
3382
|
});
|
|
3172
3383
|
const checks = base.overallStatus === "unsupported" ? base.checks : [...base.checks, ...buildWorkspaceChecks(tree)];
|
|
3173
3384
|
const report = { checks, overallStatus: base.overallStatus === "unsupported" ? base.overallStatus : computeOverallStatus(checks) };
|