@methiu/raigal 0.2.5 → 0.2.6
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 +57 -14
- package/dist/cli.js +424 -120
- package/dist/index.d.ts +1 -0
- package/dist/index.js +372 -112
- package/dist/{json-B8XGYwMv.js → json-DxIA7CCt.js} +1 -1
- package/dist/mcp.js +39 -21
- package/dist/{sarif-DqH4dGEo.js → sarif-Cu7Rm1Ma.js} +1 -1
- package/dist/{version-BlgPglaO.js → version-DtOeK-2T.js} +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire, isBuiltin } from "node:module";
|
|
3
|
+
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
3
4
|
import fs, { closeSync, existsSync, mkdtempSync, openSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
-
import { Command, Option } from "commander";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { Command, Option } from "commander";
|
|
6
8
|
import { performance } from "node:perf_hooks";
|
|
7
|
-
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
8
9
|
import os, { tmpdir } from "node:os";
|
|
9
10
|
import pc from "picocolors";
|
|
10
11
|
import wcwidth from "wcwidth";
|
|
11
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
12
|
import { z } from "zod/v4";
|
|
13
13
|
import crypto, { createHash, createPublicKey, randomUUID, verify } from "node:crypto";
|
|
14
14
|
import YAML, { parse } from "yaml";
|
|
@@ -923,7 +923,7 @@ const createLogger = (deps = {}) => {
|
|
|
923
923
|
const log = createLogger();
|
|
924
924
|
//#endregion
|
|
925
925
|
//#region src/version.ts
|
|
926
|
-
const APP_VERSION = "0.2.
|
|
926
|
+
const APP_VERSION = "0.2.6";
|
|
927
927
|
//#endregion
|
|
928
928
|
//#region src/commands/agent-connect.ts
|
|
929
929
|
const commandText = (status) => [status.provider.loginCommand.command, ...status.provider.loginCommand.args].join(" ");
|
|
@@ -1266,6 +1266,8 @@ const requestApi = async (endpoint, options = {}) => {
|
|
|
1266
1266
|
};
|
|
1267
1267
|
if (options.token) headers.Authorization = `Bearer ${options.token}`;
|
|
1268
1268
|
if (options.oidcToken) headers["X-GitHub-OIDC"] = options.oidcToken;
|
|
1269
|
+
const orgId = options.orgId || process.env.RAIGAL_ORG_ID;
|
|
1270
|
+
if (orgId) headers["X-Raigal-Org-Id"] = orgId;
|
|
1269
1271
|
let body;
|
|
1270
1272
|
if (options.body !== void 0) {
|
|
1271
1273
|
headers["Content-Type"] = "application/json";
|
|
@@ -1327,11 +1329,12 @@ const pollDeviceToken = async (deviceCode) => {
|
|
|
1327
1329
|
throw err;
|
|
1328
1330
|
}
|
|
1329
1331
|
};
|
|
1330
|
-
const fetchEntitlementFromApi = async (token, oidcToken) => {
|
|
1332
|
+
const fetchEntitlementFromApi = async (token, oidcToken, orgId) => {
|
|
1331
1333
|
const data = await requestApi("/v1/entitlement", {
|
|
1332
1334
|
method: "POST",
|
|
1333
1335
|
token: token || void 0,
|
|
1334
|
-
oidcToken
|
|
1336
|
+
oidcToken,
|
|
1337
|
+
orgId
|
|
1335
1338
|
});
|
|
1336
1339
|
return EntitlementResponse.parse(data);
|
|
1337
1340
|
};
|
|
@@ -1422,7 +1425,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
1422
1425
|
const claims = parsed.data;
|
|
1423
1426
|
const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
|
|
1424
1427
|
if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
|
|
1425
|
-
if (!isVersionAtLeast("0.2.
|
|
1428
|
+
if (!isVersionAtLeast("0.2.6", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
|
|
1426
1429
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
1427
1430
|
const grace = options.allowGraceSeconds ?? 0;
|
|
1428
1431
|
if (now > claims.exp + grace) {
|
|
@@ -1581,7 +1584,9 @@ const getEntitlement = async () => {
|
|
|
1581
1584
|
}
|
|
1582
1585
|
} else oidcToken = await fetchGitHubOidcToken();
|
|
1583
1586
|
try {
|
|
1584
|
-
const
|
|
1587
|
+
const tokenToPass = activeToken?.token ?? "";
|
|
1588
|
+
const orgId = process.env.RAIGAL_ORG_ID;
|
|
1589
|
+
const response = await fetchEntitlementFromApi(tokenToPass, oidcToken, orgId);
|
|
1585
1590
|
const claims = verifyEntitlementJwt(response.entitlement, { nowSeconds: now });
|
|
1586
1591
|
saveCachedEntitlement(response.entitlement, response.refresh_after);
|
|
1587
1592
|
return {
|
|
@@ -1595,7 +1600,7 @@ const getEntitlement = async () => {
|
|
|
1595
1600
|
};
|
|
1596
1601
|
//#endregion
|
|
1597
1602
|
//#region src/cloud/owner.ts
|
|
1598
|
-
const
|
|
1603
|
+
const GENERIC_GIT_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)[^:/]+(?::(?:\d+\/)?|[:/])|https?:\/\/(?:[^@]+@)?[^/]+\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
1599
1604
|
const parseGitRemote = (rawUrl) => {
|
|
1600
1605
|
if (!rawUrl) return {
|
|
1601
1606
|
owner: null,
|
|
@@ -1603,18 +1608,19 @@ const parseGitRemote = (rawUrl) => {
|
|
|
1603
1608
|
hasRemote: false,
|
|
1604
1609
|
isGitHubRemote: false
|
|
1605
1610
|
};
|
|
1606
|
-
const match = rawUrl.match(
|
|
1611
|
+
const match = rawUrl.match(GENERIC_GIT_REMOTE_RE);
|
|
1607
1612
|
if (!match || !match[1] || !match[2]) return {
|
|
1608
1613
|
owner: null,
|
|
1609
1614
|
repo: null,
|
|
1610
1615
|
hasRemote: true,
|
|
1611
1616
|
isGitHubRemote: false
|
|
1612
1617
|
};
|
|
1618
|
+
const isGitHub = rawUrl.includes("github.com");
|
|
1613
1619
|
return {
|
|
1614
1620
|
owner: match[1].toLowerCase(),
|
|
1615
1621
|
repo: match[2].toLowerCase(),
|
|
1616
1622
|
hasRemote: true,
|
|
1617
|
-
isGitHubRemote:
|
|
1623
|
+
isGitHubRemote: isGitHub
|
|
1618
1624
|
};
|
|
1619
1625
|
};
|
|
1620
1626
|
const readGitConfigRemote = (dir) => {
|
|
@@ -1679,6 +1685,27 @@ const detectLocalRepoOwner = (directory) => {
|
|
|
1679
1685
|
isGitHubRemote: true
|
|
1680
1686
|
};
|
|
1681
1687
|
}
|
|
1688
|
+
const gitlabRepo = process.env.CI_PROJECT_PATH?.trim();
|
|
1689
|
+
if (gitlabRepo && gitlabRepo.includes("/")) {
|
|
1690
|
+
const parts = gitlabRepo.split("/");
|
|
1691
|
+
const repo = parts.pop() ?? "";
|
|
1692
|
+
return {
|
|
1693
|
+
owner: (parts[0] ?? "").toLowerCase(),
|
|
1694
|
+
repo: repo.toLowerCase(),
|
|
1695
|
+
hasRemote: true,
|
|
1696
|
+
isGitHubRemote: false
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
const bitbucketRepo = (process.env.BITBUCKET_REPO_FULL_NAME || process.env.BITBUCKET_REPO_SLUG)?.trim();
|
|
1700
|
+
if (bitbucketRepo) {
|
|
1701
|
+
const [owner = process.env.BITBUCKET_WORKSPACE || "", repo = ""] = bitbucketRepo.includes("/") ? bitbucketRepo.split("/") : [process.env.BITBUCKET_WORKSPACE || "", bitbucketRepo];
|
|
1702
|
+
return {
|
|
1703
|
+
owner: owner.toLowerCase(),
|
|
1704
|
+
repo: repo.toLowerCase(),
|
|
1705
|
+
hasRemote: true,
|
|
1706
|
+
isGitHubRemote: false
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1682
1709
|
const gitRoot = findGitRoot$1(directory);
|
|
1683
1710
|
if (!gitRoot) return {
|
|
1684
1711
|
owner: null,
|
|
@@ -1690,26 +1717,17 @@ const detectLocalRepoOwner = (directory) => {
|
|
|
1690
1717
|
if (rawUrl !== null) return parseGitRemote(rawUrl);
|
|
1691
1718
|
return parseGitRemote(spawnGitRemote(gitRoot));
|
|
1692
1719
|
};
|
|
1693
|
-
const isNonGitHubCi = () => {
|
|
1694
|
-
const env = process.env;
|
|
1695
|
-
if (!(env.CI === "true" || env.CI === "1")) return false;
|
|
1696
|
-
return Boolean(env.GITLAB_CI || env.BITBUCKET_BUILD_NUMBER || env.CIRCLECI && !env.CIRCLE_PROJECT_REPONAME?.includes("/") || env.BUILDKITE || env.TRAVIS || env.JENKINS_URL);
|
|
1697
|
-
};
|
|
1698
1720
|
const checkOwnerAgainstAllowlist = (detected, allowedOwners, tokenKind) => {
|
|
1699
|
-
if (isNonGitHubCi()) return {
|
|
1700
|
-
allowed: false,
|
|
1701
|
-
reason: "Non-GitHub CI environments are unsupported under organization allowlists."
|
|
1702
|
-
};
|
|
1703
1721
|
if (!detected.hasRemote) {
|
|
1704
1722
|
if (tokenKind === "session") return { allowed: true };
|
|
1705
1723
|
return {
|
|
1706
1724
|
allowed: false,
|
|
1707
|
-
reason: "Repository has no
|
|
1725
|
+
reason: "Repository has no origin remote. CI and API key runs require an origin remote matching an allowed organization."
|
|
1708
1726
|
};
|
|
1709
1727
|
}
|
|
1710
|
-
if (!detected.
|
|
1728
|
+
if (!detected.owner) return {
|
|
1711
1729
|
allowed: false,
|
|
1712
|
-
reason: "Repository remote
|
|
1730
|
+
reason: "Repository remote owner could not be determined."
|
|
1713
1731
|
};
|
|
1714
1732
|
const lowerOwner = detected.owner.toLowerCase();
|
|
1715
1733
|
if (!allowedOwners.some((allowed) => allowed.login.toLowerCase() === lowerOwner)) {
|
|
@@ -1717,7 +1735,7 @@ const checkOwnerAgainstAllowlist = (detected, allowedOwners, tokenKind) => {
|
|
|
1717
1735
|
return {
|
|
1718
1736
|
allowed: false,
|
|
1719
1737
|
owner: lowerOwner,
|
|
1720
|
-
reason: `
|
|
1738
|
+
reason: `Repository owner '${detected.owner}' is not on the organization allowlist (${allowedLogins || "none"}).`
|
|
1721
1739
|
};
|
|
1722
1740
|
}
|
|
1723
1741
|
return {
|
|
@@ -2805,8 +2823,8 @@ const detectGitContext = (directory) => {
|
|
|
2805
2823
|
const eventPayload = readGitHubEventPayload();
|
|
2806
2824
|
const pr = eventPayload?.pull_request;
|
|
2807
2825
|
let headSha = pr?.head?.sha;
|
|
2808
|
-
if (!headSha) headSha = process.env.GITHUB_SHA || execGit(["rev-parse", "HEAD"], directory);
|
|
2809
|
-
let branch = process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME;
|
|
2826
|
+
if (!headSha) headSha = process.env.GITHUB_SHA || process.env.CI_COMMIT_SHA || process.env.BITBUCKET_COMMIT || execGit(["rev-parse", "HEAD"], directory);
|
|
2827
|
+
let branch = process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME || process.env.CI_COMMIT_REF_NAME || process.env.BITBUCKET_BRANCH;
|
|
2810
2828
|
if (!branch) {
|
|
2811
2829
|
branch = execGit([
|
|
2812
2830
|
"rev-parse",
|
|
@@ -2815,7 +2833,7 @@ const detectGitContext = (directory) => {
|
|
|
2815
2833
|
], directory);
|
|
2816
2834
|
if (branch === "HEAD") branch = void 0;
|
|
2817
2835
|
}
|
|
2818
|
-
let baseRef = pr?.base?.ref || process.env.GITHUB_BASE_REF;
|
|
2836
|
+
let baseRef = pr?.base?.ref || process.env.GITHUB_BASE_REF || process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME || process.env.BITBUCKET_PR_DESTINATION_BRANCH;
|
|
2819
2837
|
if (!baseRef) baseRef = void 0;
|
|
2820
2838
|
let prNumber;
|
|
2821
2839
|
if (typeof pr?.number === "number") prNumber = pr.number;
|
|
@@ -2823,6 +2841,12 @@ const detectGitContext = (directory) => {
|
|
|
2823
2841
|
else if (process.env.RAIGAL_PR_NUMBER || process.env.PR_NUMBER) {
|
|
2824
2842
|
const parsed = Number.parseInt(process.env.RAIGAL_PR_NUMBER || process.env.PR_NUMBER || "", 10);
|
|
2825
2843
|
if (!Number.isNaN(parsed) && parsed > 0) prNumber = parsed;
|
|
2844
|
+
} else if (process.env.CI_MERGE_REQUEST_IID) {
|
|
2845
|
+
const parsed = Number.parseInt(process.env.CI_MERGE_REQUEST_IID, 10);
|
|
2846
|
+
if (!Number.isNaN(parsed) && parsed > 0) prNumber = parsed;
|
|
2847
|
+
} else if (process.env.BITBUCKET_PR_ID) {
|
|
2848
|
+
const parsed = Number.parseInt(process.env.BITBUCKET_PR_ID, 10);
|
|
2849
|
+
if (!Number.isNaN(parsed) && parsed > 0) prNumber = parsed;
|
|
2826
2850
|
} else if (process.env.GITHUB_REF) {
|
|
2827
2851
|
const match = process.env.GITHUB_REF.match(/refs\/pull\/(\d+)(?:\/|$)/);
|
|
2828
2852
|
if (match && match[1]) prNumber = Number.parseInt(match[1], 10);
|
|
@@ -2879,7 +2903,31 @@ const detectCiContext = () => {
|
|
|
2879
2903
|
...runUrl ? { run_url: runUrl } : {}
|
|
2880
2904
|
};
|
|
2881
2905
|
}
|
|
2882
|
-
if (process.env.
|
|
2906
|
+
if (process.env.GITLAB_CI) {
|
|
2907
|
+
const runUrl = process.env.CI_JOB_URL || process.env.CI_PIPELINE_URL;
|
|
2908
|
+
return {
|
|
2909
|
+
provider: "gitlab-ci",
|
|
2910
|
+
...runUrl ? { run_url: runUrl } : {}
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
if (process.env.BITBUCKET_BUILD_NUMBER) {
|
|
2914
|
+
const workspace = process.env.BITBUCKET_WORKSPACE;
|
|
2915
|
+
const repoSlug = process.env.BITBUCKET_REPO_SLUG;
|
|
2916
|
+
const buildNumber = process.env.BITBUCKET_BUILD_NUMBER;
|
|
2917
|
+
const runUrl = workspace && repoSlug && buildNumber ? `https://bitbucket.org/${workspace}/${repoSlug}/pipelines/results/${buildNumber}` : void 0;
|
|
2918
|
+
return {
|
|
2919
|
+
provider: "bitbucket-pipelines",
|
|
2920
|
+
...runUrl ? { run_url: runUrl } : {}
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
if (process.env.CI === "true" || process.env.CI === "1") {
|
|
2924
|
+
const provider = process.env.CIRCLECI && "circleci" || process.env.BUILDKITE && "buildkite" || process.env.TRAVIS && "travis-ci" || "generic-ci";
|
|
2925
|
+
const runUrl = process.env.CIRCLE_BUILD_URL || process.env.BUILDKITE_BUILD_URL || process.env.TRAVIS_BUILD_WEB_URL;
|
|
2926
|
+
return {
|
|
2927
|
+
provider,
|
|
2928
|
+
...runUrl ? { run_url: runUrl } : {}
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2883
2931
|
};
|
|
2884
2932
|
//#endregion
|
|
2885
2933
|
//#region src/cloud/outbox.ts
|
|
@@ -4791,10 +4839,53 @@ jobs:
|
|
|
4791
4839
|
runs-on: ubuntu-latest
|
|
4792
4840
|
steps:
|
|
4793
4841
|
- uses: actions/checkout@v4
|
|
4842
|
+
with:
|
|
4843
|
+
fetch-depth: 0
|
|
4794
4844
|
- uses: actions/setup-node@v4
|
|
4795
4845
|
with:
|
|
4796
4846
|
node-version: 22
|
|
4797
4847
|
- run: npx --yes @methiu/raigal@latest ci .
|
|
4848
|
+
env:
|
|
4849
|
+
RAIGAL_TOKEN: \${{ secrets.RAIGAL_TOKEN }}
|
|
4850
|
+
# RAIGAL_ORG_ID: \${{ vars.RAIGAL_ORG_ID }} # Optional: required if repository owner belongs to multiple Raigal organizations
|
|
4851
|
+
`;
|
|
4852
|
+
const DEFAULT_GITLAB_CI_FILE = ".gitlab-ci.yml";
|
|
4853
|
+
const DEFAULT_GITLAB_CI_YAML = `raigal_quality_gate:
|
|
4854
|
+
image: node:24
|
|
4855
|
+
stage: test
|
|
4856
|
+
script:
|
|
4857
|
+
- if [ -n "$CI_MERGE_REQUEST_IID" ]; then npx --yes @methiu/raigal@latest ci --changes --base "$CI_MERGE_REQUEST_DIFF_BASE_SHA"; else npx --yes @methiu/raigal@latest ci .; fi
|
|
4858
|
+
variables:
|
|
4859
|
+
RAIGAL_TOKEN: $RAIGAL_TOKEN
|
|
4860
|
+
# RAIGAL_ORG_ID: $RAIGAL_ORG_ID # Optional: required if repository owner belongs to multiple Raigal organizations
|
|
4861
|
+
rules:
|
|
4862
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
4863
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
4864
|
+
`;
|
|
4865
|
+
const DEFAULT_BITBUCKET_FILE = "bitbucket-pipelines.yml";
|
|
4866
|
+
const DEFAULT_BITBUCKET_PIPELINES_YAML = `pipelines:
|
|
4867
|
+
default:
|
|
4868
|
+
- step:
|
|
4869
|
+
name: raigal quality gate
|
|
4870
|
+
image: node:24
|
|
4871
|
+
script:
|
|
4872
|
+
- npx --yes @methiu/raigal@latest ci .
|
|
4873
|
+
secrets:
|
|
4874
|
+
- RAIGAL_TOKEN
|
|
4875
|
+
# - RAIGAL_ORG_ID
|
|
4876
|
+
pull-requests:
|
|
4877
|
+
"**":
|
|
4878
|
+
- step:
|
|
4879
|
+
name: raigal quality gate
|
|
4880
|
+
image: node:24
|
|
4881
|
+
clone:
|
|
4882
|
+
depth: full
|
|
4883
|
+
script:
|
|
4884
|
+
- git fetch origin "$BITBUCKET_PR_DESTINATION_BRANCH"
|
|
4885
|
+
- npx --yes @methiu/raigal@latest ci --changes --base FETCH_HEAD
|
|
4886
|
+
secrets:
|
|
4887
|
+
- RAIGAL_TOKEN
|
|
4888
|
+
# - RAIGAL_ORG_ID
|
|
4798
4889
|
`;
|
|
4799
4890
|
const DEFAULT_RULES_YAML = `# Architecture rules (BYO)
|
|
4800
4891
|
# Uncomment and customize to enforce your project's conventions.
|
|
@@ -22658,13 +22749,21 @@ const runPiHook = async (deps = {}) => {
|
|
|
22658
22749
|
release();
|
|
22659
22750
|
}
|
|
22660
22751
|
};
|
|
22661
|
-
|
|
22752
|
+
//#endregion
|
|
22753
|
+
//#region src/hooks/assets.ts
|
|
22754
|
+
const buildRaigalMdBody = (agent) => {
|
|
22755
|
+
let onEditSection;
|
|
22756
|
+
if (agent === "claude") onEditSection = `A PostToolUse hook runs \`raigal hook claude\` after every Edit, Write, or MultiEdit. It scans the touched files and returns findings as JSON \`additionalContext\` shaped like \`RaigalFeedback\` (score, baseline, delta, regressed, counts, findings, newSinceBaseline, suggestedActions). Act on them the same turn; the \`suggestedActions\` field tells you exactly what to run next.`;
|
|
22757
|
+
else if (agent === "gemini") onEditSection = `An AfterTool hook runs \`raigal hook gemini\` after file modifications. It scans the touched files and returns findings. Act on them the same turn; fix reported issues before completing.`;
|
|
22758
|
+
else if (agent === "cursor") onEditSection = `An afterFileEdit hook runs \`raigal hook cursor\` after file modifications. It scans the touched files and reports quality findings. Fix reported errors and fixable warnings before completing your turn.`;
|
|
22759
|
+
else onEditSection = `Raigal scans changed files and reports code quality, security, and AI pattern findings. Act on reported findings during your turn; fix errors and fixable warnings before completing your response.`;
|
|
22760
|
+
return `# Raigal - agent instructions
|
|
22662
22761
|
|
|
22663
22762
|
[Raigal](https://github.com/meethiu/raigal) is your guardrail for shipping clean, maintainable, production-grade code. It catches the patterns AI coding tools leave behind: dead imports, redundant code, duplicate logic, unsafe any-typed casts, narrative comments, swallowed errors, hidden fallbacks, oversized functions, risky security constructs - and grades the project out of 100 so regressions are visible. Treat its findings as blocking, not advisory: the goal is code a human maintainer would happily inherit.
|
|
22664
22763
|
|
|
22665
22764
|
## On every edit
|
|
22666
22765
|
|
|
22667
|
-
|
|
22766
|
+
${onEditSection}
|
|
22668
22767
|
|
|
22669
22768
|
## Severity ladder
|
|
22670
22769
|
|
|
@@ -22684,15 +22783,17 @@ A PostToolUse hook runs \`raigal hook claude\` after every Edit, Write, or Multi
|
|
|
22684
22783
|
- If a finding is a false positive, leave it and explain in your reply - do not delete the rule config.
|
|
22685
22784
|
- The findings payload includes \`nextSteps[]\` - treat those as your plan for the turn.
|
|
22686
22785
|
`;
|
|
22786
|
+
};
|
|
22787
|
+
buildRaigalMdBody("claude");
|
|
22687
22788
|
//#endregion
|
|
22688
22789
|
//#region src/hooks/io/sentinel.ts
|
|
22689
22790
|
const sentinelHash = (content) => `sha256:${crypto.createHash("sha256").update(content).digest("hex").slice(0, 32)}`;
|
|
22690
|
-
const BEGIN_RE = /<!--\s*aislop:begin\s+v(\d+)(?:\s+hash=([^\s>]+))?\s*-->/;
|
|
22691
|
-
const END_RE = /<!--\s*aislop:end\s+v\d+\s*-->/;
|
|
22791
|
+
const BEGIN_RE = /<!--\s*(?:raigal|aislop):begin\s+v(\d+)(?:\s+hash=([^\s>]+))?\s*-->/;
|
|
22792
|
+
const END_RE = /<!--\s*(?:raigal|aislop):end\s+v\d+\s*-->/;
|
|
22692
22793
|
const renderFence = (body, hash) => [
|
|
22693
|
-
`<!--
|
|
22794
|
+
`<!-- raigal:begin v1 hash=${hash} -->`,
|
|
22694
22795
|
body.trimEnd(),
|
|
22695
|
-
"<!--
|
|
22796
|
+
"<!-- raigal:end v1 -->"
|
|
22696
22797
|
].join("\n");
|
|
22697
22798
|
const upsertMarkdownFence = (existing, body, hash) => {
|
|
22698
22799
|
const fenced = renderFence(body, hash);
|
|
@@ -22757,11 +22858,20 @@ const applyRemoval = (result, opts, target, nextContent) => {
|
|
|
22757
22858
|
//#region src/hooks/install/rules-only.ts
|
|
22758
22859
|
const installRulesOnly = (opts, paths, summary) => {
|
|
22759
22860
|
const result = emptyResult();
|
|
22760
|
-
const
|
|
22861
|
+
const body = paths.body ?? buildRaigalMdBody();
|
|
22862
|
+
const next = upsertMarkdownFence(readIfExists(paths.rules) ?? (paths.legacyRules ? readIfExists(paths.legacyRules) : null), body, sentinelHash(body)).nextContent;
|
|
22761
22863
|
applyContent(result, opts, paths.rules, next, summary);
|
|
22864
|
+
if (paths.legacyRules && readIfExists(paths.legacyRules) != null) {
|
|
22865
|
+
if (!opts.dryRun) try {
|
|
22866
|
+
fs.rmSync(paths.legacyRules, { force: true });
|
|
22867
|
+
} catch {}
|
|
22868
|
+
}
|
|
22762
22869
|
if (paths.host && paths.marker) {
|
|
22763
22870
|
const host = readIfExists(paths.host) ?? "";
|
|
22764
|
-
if (
|
|
22871
|
+
if (paths.legacyMarker && host.includes(paths.legacyMarker)) {
|
|
22872
|
+
const migrated = host.split("\n").map((l) => l.trim() === paths.legacyMarker ? paths.marker : l).join("\n");
|
|
22873
|
+
applyContent(result, opts, paths.host, migrated, `update ${paths.marker} reference`);
|
|
22874
|
+
} else if (!host.includes(paths.marker)) {
|
|
22765
22875
|
const joiner = host.endsWith("\n") || host.length === 0 ? "" : "\n";
|
|
22766
22876
|
const prefix = host.length === 0 ? "" : `${host}${joiner}\n`;
|
|
22767
22877
|
applyContent(result, opts, paths.host, `${prefix}${paths.marker}\n`, `append ${paths.marker} reference`);
|
|
@@ -22774,30 +22884,41 @@ const uninstallRulesOnly = (opts, paths) => {
|
|
|
22774
22884
|
removed: [],
|
|
22775
22885
|
skipped: []
|
|
22776
22886
|
};
|
|
22777
|
-
|
|
22778
|
-
|
|
22779
|
-
|
|
22887
|
+
let removedRule = false;
|
|
22888
|
+
if (readIfExists(paths.rules) != null) {
|
|
22889
|
+
applyRemoval(result, opts, paths.rules, null);
|
|
22890
|
+
removedRule = true;
|
|
22891
|
+
}
|
|
22892
|
+
if (paths.legacyRules && readIfExists(paths.legacyRules) != null) {
|
|
22893
|
+
applyRemoval(result, opts, paths.legacyRules, null);
|
|
22894
|
+
removedRule = true;
|
|
22895
|
+
}
|
|
22896
|
+
if (!removedRule) result.skipped.push(paths.rules);
|
|
22897
|
+
if (paths.host && (paths.marker || paths.legacyMarker)) {
|
|
22780
22898
|
const host = readIfExists(paths.host);
|
|
22781
|
-
if (host
|
|
22782
|
-
const stripped = host.split("\n").filter((l) => l.trim() !== paths.marker).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
22899
|
+
if (host && (paths.marker && host.includes(paths.marker) || paths.legacyMarker && host.includes(paths.legacyMarker))) {
|
|
22900
|
+
const stripped = host.split("\n").filter((l) => l.trim() !== paths.marker && l.trim() !== paths.legacyMarker).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
22783
22901
|
applyRemoval(result, opts, paths.host, stripped.length === 0 ? null : `${stripped}\n`);
|
|
22784
|
-
} else result.skipped.push(paths.host);
|
|
22902
|
+
} else if (paths.host) result.skipped.push(paths.host);
|
|
22785
22903
|
}
|
|
22786
22904
|
return result;
|
|
22787
22905
|
};
|
|
22788
22906
|
//#endregion
|
|
22789
22907
|
//#region src/hooks/install/antigravity.ts
|
|
22790
|
-
const resolveAntigravityPaths = (opts) => ({
|
|
22908
|
+
const resolveAntigravityPaths = (opts) => ({
|
|
22909
|
+
rules: path.join(opts.cwd, ".agents", "rules", "antigravity-raigal-rules.md"),
|
|
22910
|
+
legacyRules: path.join(opts.cwd, ".agents", "rules", "antigravity-aislop-rules.md")
|
|
22911
|
+
});
|
|
22791
22912
|
const installAntigravity = (opts) => {
|
|
22792
22913
|
if (opts.scope !== "project") return {
|
|
22793
22914
|
wrote: [],
|
|
22794
22915
|
skipped: [],
|
|
22795
22916
|
planned: [{
|
|
22796
|
-
path: ".agents/rules/antigravity-
|
|
22917
|
+
path: ".agents/rules/antigravity-raigal-rules.md",
|
|
22797
22918
|
summary: "Antigravity is project-scope only; pass --project"
|
|
22798
22919
|
}]
|
|
22799
22920
|
};
|
|
22800
|
-
return installRulesOnly(opts, resolveAntigravityPaths(opts), "write .agents/rules/antigravity-
|
|
22921
|
+
return installRulesOnly(opts, resolveAntigravityPaths(opts), "write .agents/rules/antigravity-raigal-rules.md");
|
|
22801
22922
|
};
|
|
22802
22923
|
const uninstallAntigravity = (opts) => {
|
|
22803
22924
|
if (opts.scope !== "project") return {
|
|
@@ -22806,9 +22927,7 @@ const uninstallAntigravity = (opts) => {
|
|
|
22806
22927
|
};
|
|
22807
22928
|
return uninstallRulesOnly(opts, resolveAntigravityPaths(opts));
|
|
22808
22929
|
};
|
|
22809
|
-
|
|
22810
|
-
//#region src/hooks/io/json-patch.ts
|
|
22811
|
-
const AISLOP_SENTINEL_KEY = "__aislop";
|
|
22930
|
+
const RAIGAL_SENTINEL_KEY = "__raigal";
|
|
22812
22931
|
const isAislopManaged = (x) => typeof x === "object" && x !== null && ("__aislop" in x && x["__aislop"] != null || "__raigal" in x && x["__raigal"] != null);
|
|
22813
22932
|
const groupIsAislop = (group) => {
|
|
22814
22933
|
if (typeof group !== "object" || group === null) return false;
|
|
@@ -22858,6 +22977,7 @@ const resolveClaudePaths = (opts) => {
|
|
|
22858
22977
|
const root = opts.scope === "project" ? path.join(opts.cwd, ".claude") : path.join(opts.home, ".claude");
|
|
22859
22978
|
return {
|
|
22860
22979
|
settings: path.join(root, "settings.json"),
|
|
22980
|
+
raigalMd: path.join(root, "RAIGAL.md"),
|
|
22861
22981
|
aislopMd: path.join(root, "AISLOP.md"),
|
|
22862
22982
|
claudeMd: path.join(root, "CLAUDE.md")
|
|
22863
22983
|
};
|
|
@@ -22872,7 +22992,7 @@ const buildHookGroup$1 = () => {
|
|
|
22872
22992
|
hooks: [{
|
|
22873
22993
|
type: "command",
|
|
22874
22994
|
command: "raigal hook claude",
|
|
22875
|
-
[
|
|
22995
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
22876
22996
|
v: 1,
|
|
22877
22997
|
managed: true,
|
|
22878
22998
|
hash: sentinelHash(hashBody)
|
|
@@ -22887,7 +23007,7 @@ const buildStopHookGroup = () => {
|
|
|
22887
23007
|
hooks: [{
|
|
22888
23008
|
type: "command",
|
|
22889
23009
|
command: "raigal hook claude --stop",
|
|
22890
|
-
[
|
|
23010
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
22891
23011
|
v: 1,
|
|
22892
23012
|
managed: true,
|
|
22893
23013
|
hash: sentinelHash(hashBody)
|
|
@@ -22906,7 +23026,7 @@ const buildFileChangedHookGroup = () => {
|
|
|
22906
23026
|
hooks: [{
|
|
22907
23027
|
type: "command",
|
|
22908
23028
|
command: "raigal hook claude --on-file-changed",
|
|
22909
|
-
[
|
|
23029
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
22910
23030
|
v: 1,
|
|
22911
23031
|
managed: true,
|
|
22912
23032
|
hash: sentinelHash(hashBody)
|
|
@@ -22932,15 +23052,25 @@ const installClaude = (opts) => {
|
|
|
22932
23052
|
const result = emptyResult();
|
|
22933
23053
|
const nextSettings = renderSettings$1(readIfExists(paths.settings), Boolean(opts.qualityGate));
|
|
22934
23054
|
applyContent(result, opts, paths.settings, nextSettings, "register PostToolUse + FileChanged hooks");
|
|
22935
|
-
const
|
|
22936
|
-
const
|
|
22937
|
-
|
|
23055
|
+
const body = buildRaigalMdBody("claude");
|
|
23056
|
+
const mdHash = sentinelHash(body);
|
|
23057
|
+
const fenced = upsertMarkdownFence(readIfExists(paths.raigalMd) ?? readIfExists(paths.aislopMd), body, mdHash);
|
|
23058
|
+
applyContent(result, opts, paths.raigalMd, fenced.nextContent, "write RAIGAL.md rules");
|
|
23059
|
+
if (paths.raigalMd !== paths.aislopMd && readIfExists(paths.aislopMd) != null) {
|
|
23060
|
+
if (!opts.dryRun) try {
|
|
23061
|
+
fs.rmSync(paths.aislopMd, { force: true });
|
|
23062
|
+
} catch {}
|
|
23063
|
+
}
|
|
22938
23064
|
const existingClaudeMd = readIfExists(paths.claudeMd) ?? "";
|
|
22939
|
-
const marker = "@
|
|
22940
|
-
|
|
23065
|
+
const marker = "@RAIGAL.md";
|
|
23066
|
+
const legacyMarker = "@AISLOP.md";
|
|
23067
|
+
if (existingClaudeMd.includes(legacyMarker)) {
|
|
23068
|
+
const migrated = existingClaudeMd.split("\n").map((line) => line.trim() === legacyMarker ? marker : line).join("\n");
|
|
23069
|
+
applyContent(result, opts, paths.claudeMd, migrated, "update @RAIGAL.md reference");
|
|
23070
|
+
} else if (!existingClaudeMd.includes(marker)) {
|
|
22941
23071
|
const joiner = existingClaudeMd.endsWith("\n") || existingClaudeMd.length === 0 ? "" : "\n";
|
|
22942
23072
|
const prefix = existingClaudeMd.length === 0 ? "" : `${existingClaudeMd}${joiner}\n`;
|
|
22943
|
-
applyContent(result, opts, paths.claudeMd, `${prefix}${marker}\n`, "append @
|
|
23073
|
+
applyContent(result, opts, paths.claudeMd, `${prefix}${marker}\n`, "append @RAIGAL.md reference");
|
|
22944
23074
|
} else result.skipped.push(paths.claudeMd);
|
|
22945
23075
|
return result;
|
|
22946
23076
|
};
|
|
@@ -22969,11 +23099,19 @@ const uninstallClaude = (opts) => {
|
|
|
22969
23099
|
if (!stillHasHooks && otherKeys.length === 0) applyRemoval(result, opts, paths.settings, null);
|
|
22970
23100
|
else applyRemoval(result, opts, paths.settings, `${JSON.stringify(stripped, null, 2)}\n`);
|
|
22971
23101
|
} else result.skipped.push(paths.settings);
|
|
22972
|
-
|
|
22973
|
-
|
|
23102
|
+
let removedMd = false;
|
|
23103
|
+
if (readIfExists(paths.raigalMd) != null) {
|
|
23104
|
+
applyRemoval(result, opts, paths.raigalMd, null);
|
|
23105
|
+
removedMd = true;
|
|
23106
|
+
}
|
|
23107
|
+
if (readIfExists(paths.aislopMd) != null) {
|
|
23108
|
+
applyRemoval(result, opts, paths.aislopMd, null);
|
|
23109
|
+
removedMd = true;
|
|
23110
|
+
}
|
|
23111
|
+
if (!removedMd) result.skipped.push(paths.raigalMd);
|
|
22974
23112
|
const claudeMd = readIfExists(paths.claudeMd);
|
|
22975
|
-
if (claudeMd?.includes("@AISLOP.md")) {
|
|
22976
|
-
const stripped = claudeMd.split("\n").filter((line) => line.trim() !== "@AISLOP.md").join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
23113
|
+
if (claudeMd?.includes("@RAIGAL.md") || claudeMd?.includes("@AISLOP.md")) {
|
|
23114
|
+
const stripped = claudeMd.split("\n").filter((line) => line.trim() !== "@RAIGAL.md" && line.trim() !== "@AISLOP.md").join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
22977
23115
|
applyRemoval(result, opts, paths.claudeMd, stripped.length === 0 ? null : `${stripped}\n`);
|
|
22978
23116
|
} else result.skipped.push(paths.claudeMd);
|
|
22979
23117
|
return result;
|
|
@@ -22981,7 +23119,10 @@ const uninstallClaude = (opts) => {
|
|
|
22981
23119
|
//#endregion
|
|
22982
23120
|
//#region src/hooks/install/cline.ts
|
|
22983
23121
|
const resolveClinePaths = (opts) => ({ rules: path.join(opts.cwd, ".clinerules") });
|
|
22984
|
-
const resolveRooPaths = (opts) => ({
|
|
23122
|
+
const resolveRooPaths = (opts) => ({
|
|
23123
|
+
rules: path.join(opts.cwd, ".roo", "rules", "raigal.md"),
|
|
23124
|
+
legacyRules: path.join(opts.cwd, ".roo", "rules", "aislop.md")
|
|
23125
|
+
});
|
|
22985
23126
|
const installCline = (opts) => {
|
|
22986
23127
|
if (opts.scope !== "project") return {
|
|
22987
23128
|
wrote: [],
|
|
@@ -22992,7 +23133,7 @@ const installCline = (opts) => {
|
|
|
22992
23133
|
}]
|
|
22993
23134
|
};
|
|
22994
23135
|
const cline = installRulesOnly(opts, resolveClinePaths(opts), "write .clinerules");
|
|
22995
|
-
const roo = installRulesOnly(opts, resolveRooPaths(opts), "write .roo/rules/
|
|
23136
|
+
const roo = installRulesOnly(opts, resolveRooPaths(opts), "write .roo/rules/raigal.md");
|
|
22996
23137
|
return {
|
|
22997
23138
|
wrote: [...cline.wrote, ...roo.wrote],
|
|
22998
23139
|
skipped: [...cline.skipped, ...roo.skipped],
|
|
@@ -23043,7 +23184,8 @@ const resolveCursorPaths = (opts) => {
|
|
|
23043
23184
|
const root = opts.scope === "project" ? path.join(opts.cwd, ".cursor") : path.join(opts.home, ".cursor");
|
|
23044
23185
|
return {
|
|
23045
23186
|
hooks: path.join(root, "hooks.json"),
|
|
23046
|
-
rules: path.join(opts.cwd, ".cursor", "rules", "
|
|
23187
|
+
rules: path.join(opts.cwd, ".cursor", "rules", "raigal.mdc"),
|
|
23188
|
+
legacyRules: path.join(opts.cwd, ".cursor", "rules", "aislop.mdc")
|
|
23047
23189
|
};
|
|
23048
23190
|
};
|
|
23049
23191
|
const buildHookEntry = () => {
|
|
@@ -23055,7 +23197,7 @@ const buildHookEntry = () => {
|
|
|
23055
23197
|
command: "raigal hook cursor",
|
|
23056
23198
|
type: "command",
|
|
23057
23199
|
timeout: 5e3,
|
|
23058
|
-
[
|
|
23200
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
23059
23201
|
v: 1,
|
|
23060
23202
|
managed: true,
|
|
23061
23203
|
hash: sentinelHash(hashBody)
|
|
@@ -23079,8 +23221,14 @@ const installCursor = (opts) => {
|
|
|
23079
23221
|
const nextHooks = renderHooksJson(readIfExists(paths.hooks));
|
|
23080
23222
|
applyContent(result, opts, paths.hooks, nextHooks, "register afterFileEdit hook");
|
|
23081
23223
|
if (opts.scope === "project") {
|
|
23082
|
-
const
|
|
23083
|
-
|
|
23224
|
+
const body = buildRaigalMdBody("cursor");
|
|
23225
|
+
const rules = upsertMarkdownFence(readIfExists(paths.rules) ?? (paths.legacyRules ? readIfExists(paths.legacyRules) : null), body, sentinelHash(body)).nextContent;
|
|
23226
|
+
applyContent(result, opts, paths.rules, rules, "write .cursor/rules/raigal.mdc");
|
|
23227
|
+
if (paths.legacyRules && readIfExists(paths.legacyRules) != null) {
|
|
23228
|
+
if (!opts.dryRun) try {
|
|
23229
|
+
fs.rmSync(paths.legacyRules, { force: true });
|
|
23230
|
+
} catch {}
|
|
23231
|
+
}
|
|
23084
23232
|
}
|
|
23085
23233
|
return result;
|
|
23086
23234
|
};
|
|
@@ -23104,7 +23252,18 @@ const uninstallCursor = (opts) => {
|
|
|
23104
23252
|
if (!stillHasHooks && otherKeys.length === 0) applyRemoval(result, opts, paths.hooks, null);
|
|
23105
23253
|
else applyRemoval(result, opts, paths.hooks, `${JSON.stringify(stripped, null, 2)}\n`);
|
|
23106
23254
|
} else result.skipped.push(paths.hooks);
|
|
23107
|
-
if (opts.scope === "project")
|
|
23255
|
+
if (opts.scope === "project") {
|
|
23256
|
+
let removedRule = false;
|
|
23257
|
+
if (readIfExists(paths.rules) != null) {
|
|
23258
|
+
applyRemoval(result, opts, paths.rules, null);
|
|
23259
|
+
removedRule = true;
|
|
23260
|
+
}
|
|
23261
|
+
if (paths.legacyRules && readIfExists(paths.legacyRules) != null) {
|
|
23262
|
+
applyRemoval(result, opts, paths.legacyRules, null);
|
|
23263
|
+
removedRule = true;
|
|
23264
|
+
}
|
|
23265
|
+
if (!removedRule) result.skipped.push(paths.rules);
|
|
23266
|
+
}
|
|
23108
23267
|
return result;
|
|
23109
23268
|
};
|
|
23110
23269
|
//#endregion
|
|
@@ -23113,6 +23272,7 @@ const resolveGeminiPaths = (opts) => {
|
|
|
23113
23272
|
const root = opts.scope === "project" ? path.join(opts.cwd, ".gemini") : path.join(opts.home, ".gemini");
|
|
23114
23273
|
return {
|
|
23115
23274
|
settings: path.join(root, "settings.json"),
|
|
23275
|
+
raigalMd: path.join(root, "RAIGAL.md"),
|
|
23116
23276
|
aislopMd: path.join(root, "AISLOP.md"),
|
|
23117
23277
|
geminiMd: path.join(root, "GEMINI.md")
|
|
23118
23278
|
};
|
|
@@ -23129,7 +23289,7 @@ const buildHookGroup = () => {
|
|
|
23129
23289
|
type: "command",
|
|
23130
23290
|
command: "raigal hook gemini",
|
|
23131
23291
|
timeout: 5e3,
|
|
23132
|
-
[
|
|
23292
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
23133
23293
|
v: 1,
|
|
23134
23294
|
managed: true,
|
|
23135
23295
|
hash: sentinelHash(hashBody)
|
|
@@ -23152,14 +23312,25 @@ const installGemini = (opts) => {
|
|
|
23152
23312
|
const result = emptyResult();
|
|
23153
23313
|
const next = renderSettings(readIfExists(paths.settings));
|
|
23154
23314
|
applyContent(result, opts, paths.settings, next, "register AfterTool hook");
|
|
23155
|
-
const
|
|
23156
|
-
|
|
23315
|
+
const body = buildRaigalMdBody("gemini");
|
|
23316
|
+
const hash = sentinelHash(body);
|
|
23317
|
+
const fenced = upsertMarkdownFence(readIfExists(paths.raigalMd) ?? readIfExists(paths.aislopMd), body, hash).nextContent;
|
|
23318
|
+
applyContent(result, opts, paths.raigalMd, fenced, "write RAIGAL.md rules");
|
|
23319
|
+
if (paths.raigalMd !== paths.aislopMd && readIfExists(paths.aislopMd) != null) {
|
|
23320
|
+
if (!opts.dryRun) try {
|
|
23321
|
+
fs.rmSync(paths.aislopMd, { force: true });
|
|
23322
|
+
} catch {}
|
|
23323
|
+
}
|
|
23157
23324
|
const existingGeminiMd = readIfExists(paths.geminiMd) ?? "";
|
|
23158
|
-
const marker = "@
|
|
23159
|
-
|
|
23325
|
+
const marker = "@RAIGAL.md";
|
|
23326
|
+
const legacyMarker = "@AISLOP.md";
|
|
23327
|
+
if (existingGeminiMd.includes(legacyMarker)) {
|
|
23328
|
+
const migrated = existingGeminiMd.split("\n").map((l) => l.trim() === legacyMarker ? marker : l).join("\n");
|
|
23329
|
+
applyContent(result, opts, paths.geminiMd, migrated, "update @RAIGAL.md reference");
|
|
23330
|
+
} else if (!existingGeminiMd.includes(marker)) {
|
|
23160
23331
|
const joiner = existingGeminiMd.endsWith("\n") || existingGeminiMd.length === 0 ? "" : "\n";
|
|
23161
23332
|
const prefix = existingGeminiMd.length === 0 ? "" : `${existingGeminiMd}${joiner}\n`;
|
|
23162
|
-
applyContent(result, opts, paths.geminiMd, `${prefix}${marker}\n`, "append @
|
|
23333
|
+
applyContent(result, opts, paths.geminiMd, `${prefix}${marker}\n`, "append @RAIGAL.md reference");
|
|
23163
23334
|
} else result.skipped.push(paths.geminiMd);
|
|
23164
23335
|
return result;
|
|
23165
23336
|
};
|
|
@@ -23183,28 +23354,39 @@ const uninstallGemini = (opts) => {
|
|
|
23183
23354
|
if (!stillHasHooks && otherKeys.length === 0) applyRemoval(result, opts, paths.settings, null);
|
|
23184
23355
|
else applyRemoval(result, opts, paths.settings, `${JSON.stringify(stripped, null, 2)}\n`);
|
|
23185
23356
|
} else result.skipped.push(paths.settings);
|
|
23186
|
-
|
|
23187
|
-
|
|
23357
|
+
let removedMd = false;
|
|
23358
|
+
if (readIfExists(paths.raigalMd) != null) {
|
|
23359
|
+
applyRemoval(result, opts, paths.raigalMd, null);
|
|
23360
|
+
removedMd = true;
|
|
23361
|
+
}
|
|
23362
|
+
if (readIfExists(paths.aislopMd) != null) {
|
|
23363
|
+
applyRemoval(result, opts, paths.aislopMd, null);
|
|
23364
|
+
removedMd = true;
|
|
23365
|
+
}
|
|
23366
|
+
if (!removedMd) result.skipped.push(paths.raigalMd);
|
|
23188
23367
|
const geminiMd = readIfExists(paths.geminiMd);
|
|
23189
|
-
if (geminiMd?.includes("@AISLOP.md")) {
|
|
23190
|
-
const stripped = geminiMd.split("\n").filter((l) => l.trim() !== "@AISLOP.md").join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
23368
|
+
if (geminiMd?.includes("@RAIGAL.md") || geminiMd?.includes("@AISLOP.md")) {
|
|
23369
|
+
const stripped = geminiMd.split("\n").filter((l) => l.trim() !== "@RAIGAL.md" && l.trim() !== "@AISLOP.md").join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
23191
23370
|
applyRemoval(result, opts, paths.geminiMd, stripped.length === 0 ? null : `${stripped}\n`);
|
|
23192
23371
|
} else result.skipped.push(paths.geminiMd);
|
|
23193
23372
|
return result;
|
|
23194
23373
|
};
|
|
23195
23374
|
//#endregion
|
|
23196
23375
|
//#region src/hooks/install/kilocode.ts
|
|
23197
|
-
const resolveKilocodePaths = (opts) => ({
|
|
23376
|
+
const resolveKilocodePaths = (opts) => ({
|
|
23377
|
+
rules: path.join(opts.cwd, ".kilocode", "rules", "raigal-rules.md"),
|
|
23378
|
+
legacyRules: path.join(opts.cwd, ".kilocode", "rules", "aislop-rules.md")
|
|
23379
|
+
});
|
|
23198
23380
|
const installKilocode = (opts) => {
|
|
23199
23381
|
if (opts.scope !== "project") return {
|
|
23200
23382
|
wrote: [],
|
|
23201
23383
|
skipped: [],
|
|
23202
23384
|
planned: [{
|
|
23203
|
-
path: ".kilocode/rules/
|
|
23385
|
+
path: ".kilocode/rules/raigal-rules.md",
|
|
23204
23386
|
summary: "Kilo Code is project-scope only; pass --project"
|
|
23205
23387
|
}]
|
|
23206
23388
|
};
|
|
23207
|
-
return installRulesOnly(opts, resolveKilocodePaths(opts), "write .kilocode/rules/
|
|
23389
|
+
return installRulesOnly(opts, resolveKilocodePaths(opts), "write .kilocode/rules/raigal-rules.md");
|
|
23208
23390
|
};
|
|
23209
23391
|
const uninstallKilocode = (opts) => {
|
|
23210
23392
|
if (opts.scope !== "project") return {
|
|
@@ -23358,18 +23540,24 @@ const paths = {
|
|
|
23358
23540
|
const p = resolveClaudePaths(opts);
|
|
23359
23541
|
return [
|
|
23360
23542
|
p.settings,
|
|
23543
|
+
p.raigalMd,
|
|
23361
23544
|
p.aislopMd,
|
|
23362
23545
|
p.claudeMd
|
|
23363
23546
|
];
|
|
23364
23547
|
},
|
|
23365
23548
|
cursor: (opts) => {
|
|
23366
23549
|
const p = resolveCursorPaths(opts);
|
|
23367
|
-
return opts.scope === "project" ? [
|
|
23550
|
+
return opts.scope === "project" ? [
|
|
23551
|
+
p.hooks,
|
|
23552
|
+
p.rules,
|
|
23553
|
+
...p.legacyRules ? [p.legacyRules] : []
|
|
23554
|
+
] : [p.hooks];
|
|
23368
23555
|
},
|
|
23369
23556
|
gemini: (opts) => {
|
|
23370
23557
|
const p = resolveGeminiPaths(opts);
|
|
23371
23558
|
return [
|
|
23372
23559
|
p.settings,
|
|
23560
|
+
p.raigalMd,
|
|
23373
23561
|
p.aislopMd,
|
|
23374
23562
|
p.geminiMd
|
|
23375
23563
|
];
|
|
@@ -23377,9 +23565,22 @@ const paths = {
|
|
|
23377
23565
|
pi: (opts) => [resolvePiPaths(opts).extension],
|
|
23378
23566
|
codex: (opts) => [resolveCodexPaths(opts).rules],
|
|
23379
23567
|
windsurf: (opts) => [resolveWindsurfPaths(opts).rules],
|
|
23380
|
-
cline: (opts) =>
|
|
23381
|
-
|
|
23382
|
-
|
|
23568
|
+
cline: (opts) => {
|
|
23569
|
+
const roo = resolveRooPaths(opts);
|
|
23570
|
+
return [
|
|
23571
|
+
resolveClinePaths(opts).rules,
|
|
23572
|
+
roo.rules,
|
|
23573
|
+
...roo.legacyRules ? [roo.legacyRules] : []
|
|
23574
|
+
];
|
|
23575
|
+
},
|
|
23576
|
+
kilocode: (opts) => {
|
|
23577
|
+
const k = resolveKilocodePaths(opts);
|
|
23578
|
+
return [k.rules, ...k.legacyRules ? [k.legacyRules] : []];
|
|
23579
|
+
},
|
|
23580
|
+
antigravity: (opts) => {
|
|
23581
|
+
const a = resolveAntigravityPaths(opts);
|
|
23582
|
+
return [a.rules, ...a.legacyRules ? [a.legacyRules] : []];
|
|
23583
|
+
},
|
|
23383
23584
|
copilot: (opts) => [resolveCopilotPaths(opts).rules]
|
|
23384
23585
|
};
|
|
23385
23586
|
const REGISTRY = {
|
|
@@ -25030,7 +25231,7 @@ const maybeNotifyUpdate = async (now = Date.now()) => {
|
|
|
25030
25231
|
if (!process.stderr.isTTY) return;
|
|
25031
25232
|
const cachePath = resolveUpdateCachePath();
|
|
25032
25233
|
const cache = readCache(cachePath);
|
|
25033
|
-
if (cache && isOutdated("0.2.
|
|
25234
|
+
if (cache && isOutdated("0.2.6", cache.latest)) process.stderr.write(formatUpdateNotice(APP_VERSION, cache.latest));
|
|
25034
25235
|
if (!cache || now - cache.checkedAt > CHECK_INTERVAL_MS) {
|
|
25035
25236
|
const latest = await fetchLatestVersion$1();
|
|
25036
25237
|
if (latest) writeCache(cachePath, {
|
|
@@ -25304,6 +25505,10 @@ const COMMAND_REFERENCE = [
|
|
|
25304
25505
|
summary: "Stop a running background agent session",
|
|
25305
25506
|
flags: ["--root <directory>", "--force"]
|
|
25306
25507
|
},
|
|
25508
|
+
{
|
|
25509
|
+
command: "raigal mcp",
|
|
25510
|
+
summary: "Run the Model Context Protocol (MCP) server over stdio"
|
|
25511
|
+
},
|
|
25307
25512
|
{
|
|
25308
25513
|
command: "raigal ci [directory]",
|
|
25309
25514
|
summary: "Run the CI quality gate with thresholded exit codes",
|
|
@@ -25483,6 +25688,7 @@ const commandGroupLabel = (command) => {
|
|
|
25483
25688
|
if (command.startsWith("raigal scan")) return "Core Workflow";
|
|
25484
25689
|
if (command.startsWith("raigal fix")) return "Core Workflow";
|
|
25485
25690
|
if (command.startsWith("raigal ci")) return "Core Workflow";
|
|
25691
|
+
if (command.startsWith("raigal mcp")) return "Local Agent";
|
|
25486
25692
|
if (command.startsWith("raigal hook")) return "Hooks";
|
|
25487
25693
|
if (command === "raigal hooks") return "Hooks";
|
|
25488
25694
|
if (command.startsWith("raigal install")) return "Hooks";
|
|
@@ -25550,7 +25756,7 @@ const renderCommandGroup = (group, commandWidth) => {
|
|
|
25550
25756
|
return lines;
|
|
25551
25757
|
};
|
|
25552
25758
|
const renderCommandReference = (input = {}) => {
|
|
25553
|
-
const version = input.version ?? "0.2.
|
|
25759
|
+
const version = input.version ?? "0.2.6";
|
|
25554
25760
|
const commandWidth = Math.max(...COMMAND_REFERENCE.map((item) => item.command.length));
|
|
25555
25761
|
const lines = [renderHeader({
|
|
25556
25762
|
version,
|
|
@@ -25773,7 +25979,7 @@ const renderHelpDetails = () => [
|
|
|
25773
25979
|
].map((line) => highlightRaigal(line, theme)).join("\n");
|
|
25774
25980
|
const renderHome = (input = {}) => {
|
|
25775
25981
|
let out = renderHeader({
|
|
25776
|
-
version: input.version ?? "0.2.
|
|
25982
|
+
version: input.version ?? "0.2.6",
|
|
25777
25983
|
command: "--bare",
|
|
25778
25984
|
context: []
|
|
25779
25985
|
});
|
|
@@ -26444,12 +26650,13 @@ const baseRefExists = (cwd, ref) => {
|
|
|
26444
26650
|
return !result.error && result.status === 0;
|
|
26445
26651
|
};
|
|
26446
26652
|
const getChangedFiles = (cwd, base) => {
|
|
26653
|
+
const trimmedBase = base?.trim();
|
|
26447
26654
|
const diff = spawnSync("git", [
|
|
26448
26655
|
"diff",
|
|
26449
26656
|
"--no-color",
|
|
26450
26657
|
"--name-only",
|
|
26451
26658
|
"--diff-filter=ACMR",
|
|
26452
|
-
|
|
26659
|
+
trimmedBase && trimmedBase.length > 0 ? trimmedBase : "HEAD"
|
|
26453
26660
|
], {
|
|
26454
26661
|
cwd,
|
|
26455
26662
|
encoding: "utf-8",
|
|
@@ -26501,13 +26708,14 @@ const listUntrackedFiles = (cwd) => {
|
|
|
26501
26708
|
return untracked.stdout.split("\n").filter((line) => line.length > 0);
|
|
26502
26709
|
};
|
|
26503
26710
|
const getChangedLineMap = (cwd, base) => {
|
|
26711
|
+
const trimmedBase = base?.trim();
|
|
26504
26712
|
const diff = spawnSync("git", [
|
|
26505
26713
|
"diff",
|
|
26506
26714
|
"--no-color",
|
|
26507
26715
|
"--no-ext-diff",
|
|
26508
26716
|
"-U0",
|
|
26509
26717
|
"--diff-filter=ACMR",
|
|
26510
|
-
|
|
26718
|
+
trimmedBase && trimmedBase.length > 0 ? trimmedBase : "HEAD"
|
|
26511
26719
|
], {
|
|
26512
26720
|
cwd,
|
|
26513
26721
|
encoding: "utf-8",
|
|
@@ -26782,10 +26990,13 @@ const isHistoryComparableScan = (options) => !options.staged && !options.changes
|
|
|
26782
26990
|
const isFullProjectScan = (options) => isHistoryComparableScan(options) && !options.include?.length && !options.exclude?.length;
|
|
26783
26991
|
const resolveScanScopeMode = (options) => {
|
|
26784
26992
|
if (options.staged) return { kind: "staged" };
|
|
26785
|
-
if (options.changes)
|
|
26786
|
-
|
|
26787
|
-
base
|
|
26788
|
-
|
|
26993
|
+
if (options.changes) {
|
|
26994
|
+
const base = options.base?.trim();
|
|
26995
|
+
return base ? {
|
|
26996
|
+
kind: "changes",
|
|
26997
|
+
base
|
|
26998
|
+
} : { kind: "changes" };
|
|
26999
|
+
}
|
|
26789
27000
|
return { kind: "full" };
|
|
26790
27001
|
};
|
|
26791
27002
|
//#endregion
|
|
@@ -27144,7 +27355,8 @@ const buildScanRender = (input) => {
|
|
|
27144
27355
|
const scanTargetError = (resolvedDir, options) => {
|
|
27145
27356
|
if (!fs.existsSync(resolvedDir)) return `Path does not exist: ${resolvedDir}`;
|
|
27146
27357
|
if (!fs.statSync(resolvedDir).isDirectory()) return `Not a directory: ${resolvedDir}`;
|
|
27147
|
-
|
|
27358
|
+
const base = options.base?.trim();
|
|
27359
|
+
if (options.changes && base && !baseRefExists(resolvedDir, base)) return `Could not resolve base ref "${base}". Make sure it exists and was fetched (e.g. \`git fetch origin ${base}\`).`;
|
|
27148
27360
|
return null;
|
|
27149
27361
|
};
|
|
27150
27362
|
//#endregion
|
|
@@ -29900,10 +30112,12 @@ const buildInitSuccessRender = (input) => {
|
|
|
29900
30112
|
}, deps);
|
|
29901
30113
|
const writtenCount = input.steps.filter((s) => s.status === "done").length;
|
|
29902
30114
|
const footer = writtenCount === 0 ? "Nothing to write" : `Done · wrote ${writtenCount} file${writtenCount === 1 ? "" : "s"}`;
|
|
29903
|
-
|
|
30115
|
+
const rail = renderRail({
|
|
29904
30116
|
steps: input.steps,
|
|
29905
30117
|
footer
|
|
29906
|
-
}, deps)
|
|
30118
|
+
}, deps);
|
|
30119
|
+
const extraHint = input.hint ? `\n${renderHintLine(input.hint, deps)}` : "";
|
|
30120
|
+
return `${header}${rail}\n${renderHintLine(`Try ${input.nextCommand}`, deps)}${extraHint}`;
|
|
29907
30121
|
};
|
|
29908
30122
|
const ENGINE_CHOICES = [
|
|
29909
30123
|
{
|
|
@@ -29954,7 +30168,45 @@ const writeGithubWorkflow = (rootDirectory, enabled) => {
|
|
|
29954
30168
|
relativePath
|
|
29955
30169
|
};
|
|
29956
30170
|
};
|
|
29957
|
-
const
|
|
30171
|
+
const writeGitlabWorkflow = (rootDirectory, enabled) => {
|
|
30172
|
+
const relativePath = DEFAULT_GITLAB_CI_FILE;
|
|
30173
|
+
if (!enabled) return { status: "declined" };
|
|
30174
|
+
const workflowPath = path.join(rootDirectory, DEFAULT_GITLAB_CI_FILE);
|
|
30175
|
+
if (fs.existsSync(workflowPath)) return {
|
|
30176
|
+
status: "skipped-exists",
|
|
30177
|
+
relativePath
|
|
30178
|
+
};
|
|
30179
|
+
fs.writeFileSync(workflowPath, DEFAULT_GITLAB_CI_YAML);
|
|
30180
|
+
return {
|
|
30181
|
+
status: "written",
|
|
30182
|
+
relativePath
|
|
30183
|
+
};
|
|
30184
|
+
};
|
|
30185
|
+
const writeBitbucketWorkflow = (rootDirectory, enabled) => {
|
|
30186
|
+
const relativePath = DEFAULT_BITBUCKET_FILE;
|
|
30187
|
+
if (!enabled) return { status: "declined" };
|
|
30188
|
+
const workflowPath = path.join(rootDirectory, DEFAULT_BITBUCKET_FILE);
|
|
30189
|
+
if (fs.existsSync(workflowPath)) return {
|
|
30190
|
+
status: "skipped-exists",
|
|
30191
|
+
relativePath
|
|
30192
|
+
};
|
|
30193
|
+
fs.writeFileSync(workflowPath, DEFAULT_BITBUCKET_PIPELINES_YAML);
|
|
30194
|
+
return {
|
|
30195
|
+
status: "written",
|
|
30196
|
+
relativePath
|
|
30197
|
+
};
|
|
30198
|
+
};
|
|
30199
|
+
const detectDefaultCiProvider = (directory) => {
|
|
30200
|
+
if (fs.existsSync(path.join(directory, ".gitlab-ci.yml"))) return "gitlab";
|
|
30201
|
+
if (fs.existsSync(path.join(directory, "bitbucket-pipelines.yml"))) return "bitbucket";
|
|
30202
|
+
if (fs.existsSync(path.join(directory, ".github/workflows"))) return "github";
|
|
30203
|
+
const detectedOwner = detectLocalRepoOwner(directory);
|
|
30204
|
+
if (detectedOwner.hasRemote) {
|
|
30205
|
+
if (detectedOwner.isGitHubRemote) return "github";
|
|
30206
|
+
}
|
|
30207
|
+
return "github";
|
|
30208
|
+
};
|
|
30209
|
+
const promptForConfigChoices = async (directory) => {
|
|
29958
30210
|
const enginesSelection = await runCancellable(() => multiselect({
|
|
29959
30211
|
message: "Which engines should run?",
|
|
29960
30212
|
options: ENGINE_CHOICES,
|
|
@@ -29984,15 +30236,26 @@ const promptForConfigChoices = async () => {
|
|
|
29984
30236
|
});
|
|
29985
30237
|
if (isCancel(telemetryChoice)) return null;
|
|
29986
30238
|
const workflowChoice = await select({
|
|
29987
|
-
message: "Add
|
|
29988
|
-
options: [
|
|
29989
|
-
|
|
29990
|
-
|
|
29991
|
-
|
|
29992
|
-
|
|
29993
|
-
|
|
29994
|
-
|
|
29995
|
-
|
|
30239
|
+
message: "Add automated CI quality gate workflow?",
|
|
30240
|
+
options: [
|
|
30241
|
+
{
|
|
30242
|
+
value: "github",
|
|
30243
|
+
label: "GitHub Actions — writes .github/workflows/raigal.yml"
|
|
30244
|
+
},
|
|
30245
|
+
{
|
|
30246
|
+
value: "gitlab",
|
|
30247
|
+
label: "GitLab CI — writes .gitlab-ci.yml"
|
|
30248
|
+
},
|
|
30249
|
+
{
|
|
30250
|
+
value: "bitbucket",
|
|
30251
|
+
label: "Bitbucket Pipelines — writes bitbucket-pipelines.yml"
|
|
30252
|
+
},
|
|
30253
|
+
{
|
|
30254
|
+
value: "none",
|
|
30255
|
+
label: "No, I'll wire CI myself"
|
|
30256
|
+
}
|
|
30257
|
+
],
|
|
30258
|
+
initialValue: detectDefaultCiProvider(directory)
|
|
29996
30259
|
});
|
|
29997
30260
|
if (isCancel(workflowChoice)) return null;
|
|
29998
30261
|
return {
|
|
@@ -30000,7 +30263,8 @@ const promptForConfigChoices = async () => {
|
|
|
30000
30263
|
failBelow: Number(failBelowRaw),
|
|
30001
30264
|
typecheck: DEFAULT_CONFIG.lint.typecheck,
|
|
30002
30265
|
telemetryEnabled: telemetryChoice === "enabled",
|
|
30003
|
-
writeGithubWorkflow: workflowChoice === "
|
|
30266
|
+
writeGithubWorkflow: workflowChoice === "github",
|
|
30267
|
+
ciProvider: workflowChoice
|
|
30004
30268
|
};
|
|
30005
30269
|
};
|
|
30006
30270
|
const strictChoices = () => ({
|
|
@@ -30008,7 +30272,8 @@ const strictChoices = () => ({
|
|
|
30008
30272
|
failBelow: 85,
|
|
30009
30273
|
typecheck: true,
|
|
30010
30274
|
telemetryEnabled: DEFAULT_CONFIG.telemetry.enabled,
|
|
30011
|
-
writeGithubWorkflow: true
|
|
30275
|
+
writeGithubWorkflow: true,
|
|
30276
|
+
ciProvider: "github"
|
|
30012
30277
|
});
|
|
30013
30278
|
const writeAislopConfig = (configDir, configPath, choices) => {
|
|
30014
30279
|
const selected = new Set(choices.engines);
|
|
@@ -30085,7 +30350,7 @@ const initCommand = async (directory, options = {}) => {
|
|
|
30085
30350
|
return;
|
|
30086
30351
|
}
|
|
30087
30352
|
}
|
|
30088
|
-
const choices = options.strict ? strictChoices() : await promptForConfigChoices();
|
|
30353
|
+
const choices = options.strict ? strictChoices() : await promptForConfigChoices(resolvedDir);
|
|
30089
30354
|
if (!choices) return;
|
|
30090
30355
|
writeAislopConfig(configDir, configPath, choices);
|
|
30091
30356
|
const steps = [{
|
|
@@ -30102,19 +30367,49 @@ const initCommand = async (directory, options = {}) => {
|
|
|
30102
30367
|
status: "skipped",
|
|
30103
30368
|
label: `${CONFIG_DIR}/${RULES_FILE} already exists`
|
|
30104
30369
|
});
|
|
30105
|
-
|
|
30106
|
-
if (
|
|
30107
|
-
|
|
30108
|
-
|
|
30109
|
-
|
|
30110
|
-
|
|
30111
|
-
|
|
30112
|
-
|
|
30113
|
-
|
|
30370
|
+
let ciHint;
|
|
30371
|
+
if (choices.ciProvider === "gitlab") {
|
|
30372
|
+
const gitlabResult = writeGitlabWorkflow(resolvedDir, true);
|
|
30373
|
+
if (gitlabResult.status === "written") {
|
|
30374
|
+
steps.push({
|
|
30375
|
+
status: "done",
|
|
30376
|
+
label: `Wrote ${gitlabResult.relativePath}`
|
|
30377
|
+
});
|
|
30378
|
+
ciHint = "Set RAIGAL_TOKEN in GitLab CI/CD Variables (Settings > CI/CD > Variables).";
|
|
30379
|
+
} else if (gitlabResult.status === "skipped-exists") steps.push({
|
|
30380
|
+
status: "skipped",
|
|
30381
|
+
label: `${gitlabResult.relativePath} already exists`
|
|
30382
|
+
});
|
|
30383
|
+
} else if (choices.ciProvider === "bitbucket") {
|
|
30384
|
+
const bbResult = writeBitbucketWorkflow(resolvedDir, true);
|
|
30385
|
+
if (bbResult.status === "written") {
|
|
30386
|
+
steps.push({
|
|
30387
|
+
status: "done",
|
|
30388
|
+
label: `Wrote ${bbResult.relativePath}`
|
|
30389
|
+
});
|
|
30390
|
+
ciHint = "Set RAIGAL_TOKEN in Bitbucket Repository Variables (Repository settings > Repository variables).";
|
|
30391
|
+
} else if (bbResult.status === "skipped-exists") steps.push({
|
|
30392
|
+
status: "skipped",
|
|
30393
|
+
label: `${bbResult.relativePath} already exists`
|
|
30394
|
+
});
|
|
30395
|
+
} else if (choices.writeGithubWorkflow || choices.ciProvider === "github") {
|
|
30396
|
+
const workflowResult = writeGithubWorkflow(resolvedDir, true);
|
|
30397
|
+
if (workflowResult.status === "written") {
|
|
30398
|
+
steps.push({
|
|
30399
|
+
status: "done",
|
|
30400
|
+
label: `Wrote ${workflowResult.relativePath}`
|
|
30401
|
+
});
|
|
30402
|
+
ciHint = "GitHub Actions zero-token OIDC enabled (permissions: id-token: write).";
|
|
30403
|
+
} else if (workflowResult.status === "skipped-exists") steps.push({
|
|
30404
|
+
status: "skipped",
|
|
30405
|
+
label: `${workflowResult.relativePath} already exists`
|
|
30406
|
+
});
|
|
30407
|
+
}
|
|
30114
30408
|
process.stdout.write(buildInitSuccessRender({
|
|
30115
30409
|
steps,
|
|
30116
30410
|
nextCommand: `${invocation} scan`,
|
|
30117
|
-
includeHeader: false
|
|
30411
|
+
includeHeader: false,
|
|
30412
|
+
hint: ciHint
|
|
30118
30413
|
}));
|
|
30119
30414
|
};
|
|
30120
30415
|
//#endregion
|
|
@@ -30446,6 +30741,15 @@ program.command("doctor [directory]").description("Check toolchain coverage for
|
|
|
30446
30741
|
return { exitCode: 0 };
|
|
30447
30742
|
});
|
|
30448
30743
|
});
|
|
30744
|
+
program.command("mcp").description("Run the Model Context Protocol (MCP) server over stdio").action(async () => {
|
|
30745
|
+
const mcpPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "mcp.js");
|
|
30746
|
+
spawn(process.execPath, [mcpPath], {
|
|
30747
|
+
stdio: "inherit",
|
|
30748
|
+
env: process.env
|
|
30749
|
+
}).on("exit", (code) => {
|
|
30750
|
+
process.exit(code ?? 0);
|
|
30751
|
+
});
|
|
30752
|
+
});
|
|
30449
30753
|
const ciProgram = program.command("ci [directory]").description("Run the quality gate for CI");
|
|
30450
30754
|
for (const [flag, description] of [
|
|
30451
30755
|
["--changes", "only gate files changed vs --base (or HEAD)"],
|