@methiu/raigal 0.2.4 → 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-BBZKvm0E.js → json-DxIA7CCt.js} +1 -1
- package/dist/mcp.js +39 -21
- package/dist/{sarif-Lt31Ld5t.js → sarif-Cu7Rm1Ma.js} +1 -1
- package/dist/{version-CukuIVX4.js → version-DtOeK-2T.js} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as APP_VERSION } from "./version-
|
|
1
|
+
import { t as APP_VERSION } from "./version-DtOeK-2T.js";
|
|
2
2
|
import { a as getEngineLabel, i as ENGINE_INFO, r as scoreImpactForRule, t as summarizeFindingAssessments } from "./finding-assessment-CGpt5YUR.js";
|
|
3
3
|
import { n as isSecretClassRule, r as maskSecrets, t as FIXED_SECRET_DIAGNOSTIC_MESSAGE } from "./mask-secrets-BXn-ZSEh.js";
|
|
4
4
|
import { createRequire, isBuiltin } from "node:module";
|
|
@@ -109,10 +109,53 @@ jobs:
|
|
|
109
109
|
runs-on: ubuntu-latest
|
|
110
110
|
steps:
|
|
111
111
|
- uses: actions/checkout@v4
|
|
112
|
+
with:
|
|
113
|
+
fetch-depth: 0
|
|
112
114
|
- uses: actions/setup-node@v4
|
|
113
115
|
with:
|
|
114
116
|
node-version: 22
|
|
115
117
|
- run: npx --yes @methiu/raigal@latest ci .
|
|
118
|
+
env:
|
|
119
|
+
RAIGAL_TOKEN: \${{ secrets.RAIGAL_TOKEN }}
|
|
120
|
+
# RAIGAL_ORG_ID: \${{ vars.RAIGAL_ORG_ID }} # Optional: required if repository owner belongs to multiple Raigal organizations
|
|
121
|
+
`;
|
|
122
|
+
const DEFAULT_GITLAB_CI_FILE = ".gitlab-ci.yml";
|
|
123
|
+
const DEFAULT_GITLAB_CI_YAML = `raigal_quality_gate:
|
|
124
|
+
image: node:24
|
|
125
|
+
stage: test
|
|
126
|
+
script:
|
|
127
|
+
- 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
|
|
128
|
+
variables:
|
|
129
|
+
RAIGAL_TOKEN: $RAIGAL_TOKEN
|
|
130
|
+
# RAIGAL_ORG_ID: $RAIGAL_ORG_ID # Optional: required if repository owner belongs to multiple Raigal organizations
|
|
131
|
+
rules:
|
|
132
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
133
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
134
|
+
`;
|
|
135
|
+
const DEFAULT_BITBUCKET_FILE = "bitbucket-pipelines.yml";
|
|
136
|
+
const DEFAULT_BITBUCKET_PIPELINES_YAML = `pipelines:
|
|
137
|
+
default:
|
|
138
|
+
- step:
|
|
139
|
+
name: raigal quality gate
|
|
140
|
+
image: node:24
|
|
141
|
+
script:
|
|
142
|
+
- npx --yes @methiu/raigal@latest ci .
|
|
143
|
+
secrets:
|
|
144
|
+
- RAIGAL_TOKEN
|
|
145
|
+
# - RAIGAL_ORG_ID
|
|
146
|
+
pull-requests:
|
|
147
|
+
"**":
|
|
148
|
+
- step:
|
|
149
|
+
name: raigal quality gate
|
|
150
|
+
image: node:24
|
|
151
|
+
clone:
|
|
152
|
+
depth: full
|
|
153
|
+
script:
|
|
154
|
+
- git fetch origin "$BITBUCKET_PR_DESTINATION_BRANCH"
|
|
155
|
+
- npx --yes @methiu/raigal@latest ci --changes --base FETCH_HEAD
|
|
156
|
+
secrets:
|
|
157
|
+
- RAIGAL_TOKEN
|
|
158
|
+
# - RAIGAL_ORG_ID
|
|
116
159
|
`;
|
|
117
160
|
const DEFAULT_RULES_YAML = `# Architecture rules (BYO)
|
|
118
161
|
# Uncomment and customize to enforce your project's conventions.
|
|
@@ -2431,12 +2474,13 @@ const baseRefExists = (cwd, ref) => {
|
|
|
2431
2474
|
return !result.error && result.status === 0;
|
|
2432
2475
|
};
|
|
2433
2476
|
const getChangedFiles = (cwd, base) => {
|
|
2477
|
+
const trimmedBase = base?.trim();
|
|
2434
2478
|
const diff = spawnSync("git", [
|
|
2435
2479
|
"diff",
|
|
2436
2480
|
"--no-color",
|
|
2437
2481
|
"--name-only",
|
|
2438
2482
|
"--diff-filter=ACMR",
|
|
2439
|
-
|
|
2483
|
+
trimmedBase && trimmedBase.length > 0 ? trimmedBase : "HEAD"
|
|
2440
2484
|
], {
|
|
2441
2485
|
cwd,
|
|
2442
2486
|
encoding: "utf-8",
|
|
@@ -2488,13 +2532,14 @@ const listUntrackedFiles = (cwd) => {
|
|
|
2488
2532
|
return untracked.stdout.split("\n").filter((line) => line.length > 0);
|
|
2489
2533
|
};
|
|
2490
2534
|
const getChangedLineMap = (cwd, base) => {
|
|
2535
|
+
const trimmedBase = base?.trim();
|
|
2491
2536
|
const diff = spawnSync("git", [
|
|
2492
2537
|
"diff",
|
|
2493
2538
|
"--no-color",
|
|
2494
2539
|
"--no-ext-diff",
|
|
2495
2540
|
"-U0",
|
|
2496
2541
|
"--diff-filter=ACMR",
|
|
2497
|
-
|
|
2542
|
+
trimmedBase && trimmedBase.length > 0 ? trimmedBase : "HEAD"
|
|
2498
2543
|
], {
|
|
2499
2544
|
cwd,
|
|
2500
2545
|
encoding: "utf-8",
|
|
@@ -16323,6 +16368,8 @@ const requestApi = async (endpoint, options = {}) => {
|
|
|
16323
16368
|
};
|
|
16324
16369
|
if (options.token) headers.Authorization = `Bearer ${options.token}`;
|
|
16325
16370
|
if (options.oidcToken) headers["X-GitHub-OIDC"] = options.oidcToken;
|
|
16371
|
+
const orgId = options.orgId || process.env.RAIGAL_ORG_ID;
|
|
16372
|
+
if (orgId) headers["X-Raigal-Org-Id"] = orgId;
|
|
16326
16373
|
let body;
|
|
16327
16374
|
if (options.body !== void 0) {
|
|
16328
16375
|
headers["Content-Type"] = "application/json";
|
|
@@ -16354,11 +16401,12 @@ const requestApi = async (endpoint, options = {}) => {
|
|
|
16354
16401
|
}
|
|
16355
16402
|
return json;
|
|
16356
16403
|
};
|
|
16357
|
-
const fetchEntitlementFromApi = async (token, oidcToken) => {
|
|
16404
|
+
const fetchEntitlementFromApi = async (token, oidcToken, orgId) => {
|
|
16358
16405
|
const data = await requestApi("/v1/entitlement", {
|
|
16359
16406
|
method: "POST",
|
|
16360
16407
|
token: token || void 0,
|
|
16361
|
-
oidcToken
|
|
16408
|
+
oidcToken,
|
|
16409
|
+
orgId
|
|
16362
16410
|
});
|
|
16363
16411
|
return EntitlementResponse.parse(data);
|
|
16364
16412
|
};
|
|
@@ -16440,7 +16488,7 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
16440
16488
|
const claims = parsed.data;
|
|
16441
16489
|
const expectedIssuer = process.env.RAIGAL_ISSUER?.trim() || "https://app.raigal.dev";
|
|
16442
16490
|
if (claims.iss !== expectedIssuer) throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
|
|
16443
|
-
if (!isVersionAtLeast("0.2.
|
|
16491
|
+
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");
|
|
16444
16492
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
16445
16493
|
const grace = options.allowGraceSeconds ?? 0;
|
|
16446
16494
|
if (now > claims.exp + grace) {
|
|
@@ -16599,7 +16647,9 @@ const getEntitlement = async () => {
|
|
|
16599
16647
|
}
|
|
16600
16648
|
} else oidcToken = await fetchGitHubOidcToken();
|
|
16601
16649
|
try {
|
|
16602
|
-
const
|
|
16650
|
+
const tokenToPass = activeToken?.token ?? "";
|
|
16651
|
+
const orgId = process.env.RAIGAL_ORG_ID;
|
|
16652
|
+
const response = await fetchEntitlementFromApi(tokenToPass, oidcToken, orgId);
|
|
16603
16653
|
const claims = verifyEntitlementJwt(response.entitlement, { nowSeconds: now });
|
|
16604
16654
|
saveCachedEntitlement(response.entitlement, response.refresh_after);
|
|
16605
16655
|
return {
|
|
@@ -16613,7 +16663,7 @@ const getEntitlement = async () => {
|
|
|
16613
16663
|
};
|
|
16614
16664
|
//#endregion
|
|
16615
16665
|
//#region src/cloud/owner.ts
|
|
16616
|
-
const
|
|
16666
|
+
const GENERIC_GIT_REMOTE_RE = /^(?:(?:git@|ssh:\/\/(?:git@)?)[^:/]+(?::(?:\d+\/)?|[:/])|https?:\/\/(?:[^@]+@)?[^/]+\/)([^/]+)\/([^/\s]+?)(?:\.git)?\s*$/;
|
|
16617
16667
|
const parseGitRemote = (rawUrl) => {
|
|
16618
16668
|
if (!rawUrl) return {
|
|
16619
16669
|
owner: null,
|
|
@@ -16621,18 +16671,19 @@ const parseGitRemote = (rawUrl) => {
|
|
|
16621
16671
|
hasRemote: false,
|
|
16622
16672
|
isGitHubRemote: false
|
|
16623
16673
|
};
|
|
16624
|
-
const match = rawUrl.match(
|
|
16674
|
+
const match = rawUrl.match(GENERIC_GIT_REMOTE_RE);
|
|
16625
16675
|
if (!match || !match[1] || !match[2]) return {
|
|
16626
16676
|
owner: null,
|
|
16627
16677
|
repo: null,
|
|
16628
16678
|
hasRemote: true,
|
|
16629
16679
|
isGitHubRemote: false
|
|
16630
16680
|
};
|
|
16681
|
+
const isGitHub = rawUrl.includes("github.com");
|
|
16631
16682
|
return {
|
|
16632
16683
|
owner: match[1].toLowerCase(),
|
|
16633
16684
|
repo: match[2].toLowerCase(),
|
|
16634
16685
|
hasRemote: true,
|
|
16635
|
-
isGitHubRemote:
|
|
16686
|
+
isGitHubRemote: isGitHub
|
|
16636
16687
|
};
|
|
16637
16688
|
};
|
|
16638
16689
|
const readGitConfigRemote = (dir) => {
|
|
@@ -16697,6 +16748,27 @@ const detectLocalRepoOwner = (directory) => {
|
|
|
16697
16748
|
isGitHubRemote: true
|
|
16698
16749
|
};
|
|
16699
16750
|
}
|
|
16751
|
+
const gitlabRepo = process.env.CI_PROJECT_PATH?.trim();
|
|
16752
|
+
if (gitlabRepo && gitlabRepo.includes("/")) {
|
|
16753
|
+
const parts = gitlabRepo.split("/");
|
|
16754
|
+
const repo = parts.pop() ?? "";
|
|
16755
|
+
return {
|
|
16756
|
+
owner: (parts[0] ?? "").toLowerCase(),
|
|
16757
|
+
repo: repo.toLowerCase(),
|
|
16758
|
+
hasRemote: true,
|
|
16759
|
+
isGitHubRemote: false
|
|
16760
|
+
};
|
|
16761
|
+
}
|
|
16762
|
+
const bitbucketRepo = (process.env.BITBUCKET_REPO_FULL_NAME || process.env.BITBUCKET_REPO_SLUG)?.trim();
|
|
16763
|
+
if (bitbucketRepo) {
|
|
16764
|
+
const [owner = process.env.BITBUCKET_WORKSPACE || "", repo = ""] = bitbucketRepo.includes("/") ? bitbucketRepo.split("/") : [process.env.BITBUCKET_WORKSPACE || "", bitbucketRepo];
|
|
16765
|
+
return {
|
|
16766
|
+
owner: owner.toLowerCase(),
|
|
16767
|
+
repo: repo.toLowerCase(),
|
|
16768
|
+
hasRemote: true,
|
|
16769
|
+
isGitHubRemote: false
|
|
16770
|
+
};
|
|
16771
|
+
}
|
|
16700
16772
|
const gitRoot = findGitRoot(directory);
|
|
16701
16773
|
if (!gitRoot) return {
|
|
16702
16774
|
owner: null,
|
|
@@ -16708,26 +16780,17 @@ const detectLocalRepoOwner = (directory) => {
|
|
|
16708
16780
|
if (rawUrl !== null) return parseGitRemote(rawUrl);
|
|
16709
16781
|
return parseGitRemote(spawnGitRemote(gitRoot));
|
|
16710
16782
|
};
|
|
16711
|
-
const isNonGitHubCi = () => {
|
|
16712
|
-
const env = process.env;
|
|
16713
|
-
if (!(env.CI === "true" || env.CI === "1")) return false;
|
|
16714
|
-
return Boolean(env.GITLAB_CI || env.BITBUCKET_BUILD_NUMBER || env.CIRCLECI && !env.CIRCLE_PROJECT_REPONAME?.includes("/") || env.BUILDKITE || env.TRAVIS || env.JENKINS_URL);
|
|
16715
|
-
};
|
|
16716
16783
|
const checkOwnerAgainstAllowlist = (detected, allowedOwners, tokenKind) => {
|
|
16717
|
-
if (isNonGitHubCi()) return {
|
|
16718
|
-
allowed: false,
|
|
16719
|
-
reason: "Non-GitHub CI environments are unsupported under organization allowlists."
|
|
16720
|
-
};
|
|
16721
16784
|
if (!detected.hasRemote) {
|
|
16722
16785
|
if (tokenKind === "session") return { allowed: true };
|
|
16723
16786
|
return {
|
|
16724
16787
|
allowed: false,
|
|
16725
|
-
reason: "Repository has no
|
|
16788
|
+
reason: "Repository has no origin remote. CI and API key runs require an origin remote matching an allowed organization."
|
|
16726
16789
|
};
|
|
16727
16790
|
}
|
|
16728
|
-
if (!detected.
|
|
16791
|
+
if (!detected.owner) return {
|
|
16729
16792
|
allowed: false,
|
|
16730
|
-
reason: "Repository remote
|
|
16793
|
+
reason: "Repository remote owner could not be determined."
|
|
16731
16794
|
};
|
|
16732
16795
|
const lowerOwner = detected.owner.toLowerCase();
|
|
16733
16796
|
if (!allowedOwners.some((allowed) => allowed.login.toLowerCase() === lowerOwner)) {
|
|
@@ -16735,7 +16798,7 @@ const checkOwnerAgainstAllowlist = (detected, allowedOwners, tokenKind) => {
|
|
|
16735
16798
|
return {
|
|
16736
16799
|
allowed: false,
|
|
16737
16800
|
owner: lowerOwner,
|
|
16738
|
-
reason: `
|
|
16801
|
+
reason: `Repository owner '${detected.owner}' is not on the organization allowlist (${allowedLogins || "none"}).`
|
|
16739
16802
|
};
|
|
16740
16803
|
}
|
|
16741
16804
|
return {
|
|
@@ -17990,10 +18053,13 @@ const isHistoryComparableScan = (options) => !options.staged && !options.changes
|
|
|
17990
18053
|
const isFullProjectScan = (options) => isHistoryComparableScan(options) && !options.include?.length && !options.exclude?.length;
|
|
17991
18054
|
const resolveScanScopeMode = (options) => {
|
|
17992
18055
|
if (options.staged) return { kind: "staged" };
|
|
17993
|
-
if (options.changes)
|
|
17994
|
-
|
|
17995
|
-
base
|
|
17996
|
-
|
|
18056
|
+
if (options.changes) {
|
|
18057
|
+
const base = options.base?.trim();
|
|
18058
|
+
return base ? {
|
|
18059
|
+
kind: "changes",
|
|
18060
|
+
base
|
|
18061
|
+
} : { kind: "changes" };
|
|
18062
|
+
}
|
|
17997
18063
|
return { kind: "full" };
|
|
17998
18064
|
};
|
|
17999
18065
|
//#endregion
|
|
@@ -18001,7 +18067,8 @@ const resolveScanScopeMode = (options) => {
|
|
|
18001
18067
|
const scanTargetError = (resolvedDir, options) => {
|
|
18002
18068
|
if (!fs.existsSync(resolvedDir)) return `Path does not exist: ${resolvedDir}`;
|
|
18003
18069
|
if (!fs.statSync(resolvedDir).isDirectory()) return `Not a directory: ${resolvedDir}`;
|
|
18004
|
-
|
|
18070
|
+
const base = options.base?.trim();
|
|
18071
|
+
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}\`).`;
|
|
18005
18072
|
return null;
|
|
18006
18073
|
};
|
|
18007
18074
|
//#endregion
|
|
@@ -18656,13 +18723,21 @@ const recordFullScanActivity = (history, allowPilotInvitation, telemetryConfig)
|
|
|
18656
18723
|
appendHistory(history);
|
|
18657
18724
|
return allowPilotInvitation && !isHistoryDisabled() && !isTelemetryDisabled(telemetryConfig) && recordCompletedFullScan();
|
|
18658
18725
|
};
|
|
18659
|
-
|
|
18726
|
+
//#endregion
|
|
18727
|
+
//#region src/hooks/assets.ts
|
|
18728
|
+
const buildRaigalMdBody = (agent) => {
|
|
18729
|
+
let onEditSection;
|
|
18730
|
+
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.`;
|
|
18731
|
+
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.`;
|
|
18732
|
+
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.`;
|
|
18733
|
+
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.`;
|
|
18734
|
+
return `# Raigal - agent instructions
|
|
18660
18735
|
|
|
18661
18736
|
[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.
|
|
18662
18737
|
|
|
18663
18738
|
## On every edit
|
|
18664
18739
|
|
|
18665
|
-
|
|
18740
|
+
${onEditSection}
|
|
18666
18741
|
|
|
18667
18742
|
## Severity ladder
|
|
18668
18743
|
|
|
@@ -18682,6 +18757,8 @@ A PostToolUse hook runs \`raigal hook claude\` after every Edit, Write, or Multi
|
|
|
18682
18757
|
- If a finding is a false positive, leave it and explain in your reply - do not delete the rule config.
|
|
18683
18758
|
- The findings payload includes \`nextSteps[]\` - treat those as your plan for the turn.
|
|
18684
18759
|
`;
|
|
18760
|
+
};
|
|
18761
|
+
buildRaigalMdBody("claude");
|
|
18685
18762
|
//#endregion
|
|
18686
18763
|
//#region src/hooks/io/atomic-write.ts
|
|
18687
18764
|
const atomicWrite = (targetPath, content) => {
|
|
@@ -18712,12 +18789,12 @@ const readIfExists = (targetPath) => {
|
|
|
18712
18789
|
//#endregion
|
|
18713
18790
|
//#region src/hooks/io/sentinel.ts
|
|
18714
18791
|
const sentinelHash = (content) => `sha256:${crypto.createHash("sha256").update(content).digest("hex").slice(0, 32)}`;
|
|
18715
|
-
const BEGIN_RE = /<!--\s*aislop:begin\s+v(\d+)(?:\s+hash=([^\s>]+))?\s*-->/;
|
|
18716
|
-
const END_RE = /<!--\s*aislop:end\s+v\d+\s*-->/;
|
|
18792
|
+
const BEGIN_RE = /<!--\s*(?:raigal|aislop):begin\s+v(\d+)(?:\s+hash=([^\s>]+))?\s*-->/;
|
|
18793
|
+
const END_RE = /<!--\s*(?:raigal|aislop):end\s+v\d+\s*-->/;
|
|
18717
18794
|
const renderFence = (body, hash) => [
|
|
18718
|
-
`<!--
|
|
18795
|
+
`<!-- raigal:begin v1 hash=${hash} -->`,
|
|
18719
18796
|
body.trimEnd(),
|
|
18720
|
-
"<!--
|
|
18797
|
+
"<!-- raigal:end v1 -->"
|
|
18721
18798
|
].join("\n");
|
|
18722
18799
|
const upsertMarkdownFence = (existing, body, hash) => {
|
|
18723
18800
|
const fenced = renderFence(body, hash);
|
|
@@ -18782,11 +18859,20 @@ const applyRemoval = (result, opts, target, nextContent) => {
|
|
|
18782
18859
|
//#region src/hooks/install/rules-only.ts
|
|
18783
18860
|
const installRulesOnly = (opts, paths, summary) => {
|
|
18784
18861
|
const result = emptyResult();
|
|
18785
|
-
const
|
|
18862
|
+
const body = paths.body ?? buildRaigalMdBody();
|
|
18863
|
+
const next = upsertMarkdownFence(readIfExists(paths.rules) ?? (paths.legacyRules ? readIfExists(paths.legacyRules) : null), body, sentinelHash(body)).nextContent;
|
|
18786
18864
|
applyContent(result, opts, paths.rules, next, summary);
|
|
18865
|
+
if (paths.legacyRules && readIfExists(paths.legacyRules) != null) {
|
|
18866
|
+
if (!opts.dryRun) try {
|
|
18867
|
+
fs.rmSync(paths.legacyRules, { force: true });
|
|
18868
|
+
} catch {}
|
|
18869
|
+
}
|
|
18787
18870
|
if (paths.host && paths.marker) {
|
|
18788
18871
|
const host = readIfExists(paths.host) ?? "";
|
|
18789
|
-
if (
|
|
18872
|
+
if (paths.legacyMarker && host.includes(paths.legacyMarker)) {
|
|
18873
|
+
const migrated = host.split("\n").map((l) => l.trim() === paths.legacyMarker ? paths.marker : l).join("\n");
|
|
18874
|
+
applyContent(result, opts, paths.host, migrated, `update ${paths.marker} reference`);
|
|
18875
|
+
} else if (!host.includes(paths.marker)) {
|
|
18790
18876
|
const joiner = host.endsWith("\n") || host.length === 0 ? "" : "\n";
|
|
18791
18877
|
const prefix = host.length === 0 ? "" : `${host}${joiner}\n`;
|
|
18792
18878
|
applyContent(result, opts, paths.host, `${prefix}${paths.marker}\n`, `append ${paths.marker} reference`);
|
|
@@ -18799,30 +18885,41 @@ const uninstallRulesOnly = (opts, paths) => {
|
|
|
18799
18885
|
removed: [],
|
|
18800
18886
|
skipped: []
|
|
18801
18887
|
};
|
|
18802
|
-
|
|
18803
|
-
|
|
18804
|
-
|
|
18888
|
+
let removedRule = false;
|
|
18889
|
+
if (readIfExists(paths.rules) != null) {
|
|
18890
|
+
applyRemoval(result, opts, paths.rules, null);
|
|
18891
|
+
removedRule = true;
|
|
18892
|
+
}
|
|
18893
|
+
if (paths.legacyRules && readIfExists(paths.legacyRules) != null) {
|
|
18894
|
+
applyRemoval(result, opts, paths.legacyRules, null);
|
|
18895
|
+
removedRule = true;
|
|
18896
|
+
}
|
|
18897
|
+
if (!removedRule) result.skipped.push(paths.rules);
|
|
18898
|
+
if (paths.host && (paths.marker || paths.legacyMarker)) {
|
|
18805
18899
|
const host = readIfExists(paths.host);
|
|
18806
|
-
if (host
|
|
18807
|
-
const stripped = host.split("\n").filter((l) => l.trim() !== paths.marker).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
18900
|
+
if (host && (paths.marker && host.includes(paths.marker) || paths.legacyMarker && host.includes(paths.legacyMarker))) {
|
|
18901
|
+
const stripped = host.split("\n").filter((l) => l.trim() !== paths.marker && l.trim() !== paths.legacyMarker).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
18808
18902
|
applyRemoval(result, opts, paths.host, stripped.length === 0 ? null : `${stripped}\n`);
|
|
18809
|
-
} else result.skipped.push(paths.host);
|
|
18903
|
+
} else if (paths.host) result.skipped.push(paths.host);
|
|
18810
18904
|
}
|
|
18811
18905
|
return result;
|
|
18812
18906
|
};
|
|
18813
18907
|
//#endregion
|
|
18814
18908
|
//#region src/hooks/install/antigravity.ts
|
|
18815
|
-
const resolveAntigravityPaths = (opts) => ({
|
|
18909
|
+
const resolveAntigravityPaths = (opts) => ({
|
|
18910
|
+
rules: path.join(opts.cwd, ".agents", "rules", "antigravity-raigal-rules.md"),
|
|
18911
|
+
legacyRules: path.join(opts.cwd, ".agents", "rules", "antigravity-aislop-rules.md")
|
|
18912
|
+
});
|
|
18816
18913
|
const installAntigravity = (opts) => {
|
|
18817
18914
|
if (opts.scope !== "project") return {
|
|
18818
18915
|
wrote: [],
|
|
18819
18916
|
skipped: [],
|
|
18820
18917
|
planned: [{
|
|
18821
|
-
path: ".agents/rules/antigravity-
|
|
18918
|
+
path: ".agents/rules/antigravity-raigal-rules.md",
|
|
18822
18919
|
summary: "Antigravity is project-scope only; pass --project"
|
|
18823
18920
|
}]
|
|
18824
18921
|
};
|
|
18825
|
-
return installRulesOnly(opts, resolveAntigravityPaths(opts), "write .agents/rules/antigravity-
|
|
18922
|
+
return installRulesOnly(opts, resolveAntigravityPaths(opts), "write .agents/rules/antigravity-raigal-rules.md");
|
|
18826
18923
|
};
|
|
18827
18924
|
const uninstallAntigravity = (opts) => {
|
|
18828
18925
|
if (opts.scope !== "project") return {
|
|
@@ -18831,9 +18928,7 @@ const uninstallAntigravity = (opts) => {
|
|
|
18831
18928
|
};
|
|
18832
18929
|
return uninstallRulesOnly(opts, resolveAntigravityPaths(opts));
|
|
18833
18930
|
};
|
|
18834
|
-
|
|
18835
|
-
//#region src/hooks/io/json-patch.ts
|
|
18836
|
-
const AISLOP_SENTINEL_KEY = "__aislop";
|
|
18931
|
+
const RAIGAL_SENTINEL_KEY = "__raigal";
|
|
18837
18932
|
const isAislopManaged = (x) => typeof x === "object" && x !== null && ("__aislop" in x && x["__aislop"] != null || "__raigal" in x && x["__raigal"] != null);
|
|
18838
18933
|
const groupIsAislop = (group) => {
|
|
18839
18934
|
if (typeof group !== "object" || group === null) return false;
|
|
@@ -18883,6 +18978,7 @@ const resolveClaudePaths = (opts) => {
|
|
|
18883
18978
|
const root = opts.scope === "project" ? path.join(opts.cwd, ".claude") : path.join(opts.home, ".claude");
|
|
18884
18979
|
return {
|
|
18885
18980
|
settings: path.join(root, "settings.json"),
|
|
18981
|
+
raigalMd: path.join(root, "RAIGAL.md"),
|
|
18886
18982
|
aislopMd: path.join(root, "AISLOP.md"),
|
|
18887
18983
|
claudeMd: path.join(root, "CLAUDE.md")
|
|
18888
18984
|
};
|
|
@@ -18897,7 +18993,7 @@ const buildHookGroup$1 = () => {
|
|
|
18897
18993
|
hooks: [{
|
|
18898
18994
|
type: "command",
|
|
18899
18995
|
command: "raigal hook claude",
|
|
18900
|
-
[
|
|
18996
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
18901
18997
|
v: 1,
|
|
18902
18998
|
managed: true,
|
|
18903
18999
|
hash: sentinelHash(hashBody)
|
|
@@ -18912,7 +19008,7 @@ const buildStopHookGroup = () => {
|
|
|
18912
19008
|
hooks: [{
|
|
18913
19009
|
type: "command",
|
|
18914
19010
|
command: "raigal hook claude --stop",
|
|
18915
|
-
[
|
|
19011
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
18916
19012
|
v: 1,
|
|
18917
19013
|
managed: true,
|
|
18918
19014
|
hash: sentinelHash(hashBody)
|
|
@@ -18931,7 +19027,7 @@ const buildFileChangedHookGroup = () => {
|
|
|
18931
19027
|
hooks: [{
|
|
18932
19028
|
type: "command",
|
|
18933
19029
|
command: "raigal hook claude --on-file-changed",
|
|
18934
|
-
[
|
|
19030
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
18935
19031
|
v: 1,
|
|
18936
19032
|
managed: true,
|
|
18937
19033
|
hash: sentinelHash(hashBody)
|
|
@@ -18957,15 +19053,25 @@ const installClaude = (opts) => {
|
|
|
18957
19053
|
const result = emptyResult();
|
|
18958
19054
|
const nextSettings = renderSettings$1(readIfExists(paths.settings), Boolean(opts.qualityGate));
|
|
18959
19055
|
applyContent(result, opts, paths.settings, nextSettings, "register PostToolUse + FileChanged hooks");
|
|
18960
|
-
const
|
|
18961
|
-
const
|
|
18962
|
-
|
|
19056
|
+
const body = buildRaigalMdBody("claude");
|
|
19057
|
+
const mdHash = sentinelHash(body);
|
|
19058
|
+
const fenced = upsertMarkdownFence(readIfExists(paths.raigalMd) ?? readIfExists(paths.aislopMd), body, mdHash);
|
|
19059
|
+
applyContent(result, opts, paths.raigalMd, fenced.nextContent, "write RAIGAL.md rules");
|
|
19060
|
+
if (paths.raigalMd !== paths.aislopMd && readIfExists(paths.aislopMd) != null) {
|
|
19061
|
+
if (!opts.dryRun) try {
|
|
19062
|
+
fs.rmSync(paths.aislopMd, { force: true });
|
|
19063
|
+
} catch {}
|
|
19064
|
+
}
|
|
18963
19065
|
const existingClaudeMd = readIfExists(paths.claudeMd) ?? "";
|
|
18964
|
-
const marker = "@
|
|
18965
|
-
|
|
19066
|
+
const marker = "@RAIGAL.md";
|
|
19067
|
+
const legacyMarker = "@AISLOP.md";
|
|
19068
|
+
if (existingClaudeMd.includes(legacyMarker)) {
|
|
19069
|
+
const migrated = existingClaudeMd.split("\n").map((line) => line.trim() === legacyMarker ? marker : line).join("\n");
|
|
19070
|
+
applyContent(result, opts, paths.claudeMd, migrated, "update @RAIGAL.md reference");
|
|
19071
|
+
} else if (!existingClaudeMd.includes(marker)) {
|
|
18966
19072
|
const joiner = existingClaudeMd.endsWith("\n") || existingClaudeMd.length === 0 ? "" : "\n";
|
|
18967
19073
|
const prefix = existingClaudeMd.length === 0 ? "" : `${existingClaudeMd}${joiner}\n`;
|
|
18968
|
-
applyContent(result, opts, paths.claudeMd, `${prefix}${marker}\n`, "append @
|
|
19074
|
+
applyContent(result, opts, paths.claudeMd, `${prefix}${marker}\n`, "append @RAIGAL.md reference");
|
|
18969
19075
|
} else result.skipped.push(paths.claudeMd);
|
|
18970
19076
|
return result;
|
|
18971
19077
|
};
|
|
@@ -18994,11 +19100,19 @@ const uninstallClaude = (opts) => {
|
|
|
18994
19100
|
if (!stillHasHooks && otherKeys.length === 0) applyRemoval(result, opts, paths.settings, null);
|
|
18995
19101
|
else applyRemoval(result, opts, paths.settings, `${JSON.stringify(stripped, null, 2)}\n`);
|
|
18996
19102
|
} else result.skipped.push(paths.settings);
|
|
18997
|
-
|
|
18998
|
-
|
|
19103
|
+
let removedMd = false;
|
|
19104
|
+
if (readIfExists(paths.raigalMd) != null) {
|
|
19105
|
+
applyRemoval(result, opts, paths.raigalMd, null);
|
|
19106
|
+
removedMd = true;
|
|
19107
|
+
}
|
|
19108
|
+
if (readIfExists(paths.aislopMd) != null) {
|
|
19109
|
+
applyRemoval(result, opts, paths.aislopMd, null);
|
|
19110
|
+
removedMd = true;
|
|
19111
|
+
}
|
|
19112
|
+
if (!removedMd) result.skipped.push(paths.raigalMd);
|
|
18999
19113
|
const claudeMd = readIfExists(paths.claudeMd);
|
|
19000
|
-
if (claudeMd?.includes("@AISLOP.md")) {
|
|
19001
|
-
const stripped = claudeMd.split("\n").filter((line) => line.trim() !== "@AISLOP.md").join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
19114
|
+
if (claudeMd?.includes("@RAIGAL.md") || claudeMd?.includes("@AISLOP.md")) {
|
|
19115
|
+
const stripped = claudeMd.split("\n").filter((line) => line.trim() !== "@RAIGAL.md" && line.trim() !== "@AISLOP.md").join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
19002
19116
|
applyRemoval(result, opts, paths.claudeMd, stripped.length === 0 ? null : `${stripped}\n`);
|
|
19003
19117
|
} else result.skipped.push(paths.claudeMd);
|
|
19004
19118
|
return result;
|
|
@@ -19006,7 +19120,10 @@ const uninstallClaude = (opts) => {
|
|
|
19006
19120
|
//#endregion
|
|
19007
19121
|
//#region src/hooks/install/cline.ts
|
|
19008
19122
|
const resolveClinePaths = (opts) => ({ rules: path.join(opts.cwd, ".clinerules") });
|
|
19009
|
-
const resolveRooPaths = (opts) => ({
|
|
19123
|
+
const resolveRooPaths = (opts) => ({
|
|
19124
|
+
rules: path.join(opts.cwd, ".roo", "rules", "raigal.md"),
|
|
19125
|
+
legacyRules: path.join(opts.cwd, ".roo", "rules", "aislop.md")
|
|
19126
|
+
});
|
|
19010
19127
|
const installCline = (opts) => {
|
|
19011
19128
|
if (opts.scope !== "project") return {
|
|
19012
19129
|
wrote: [],
|
|
@@ -19017,7 +19134,7 @@ const installCline = (opts) => {
|
|
|
19017
19134
|
}]
|
|
19018
19135
|
};
|
|
19019
19136
|
const cline = installRulesOnly(opts, resolveClinePaths(opts), "write .clinerules");
|
|
19020
|
-
const roo = installRulesOnly(opts, resolveRooPaths(opts), "write .roo/rules/
|
|
19137
|
+
const roo = installRulesOnly(opts, resolveRooPaths(opts), "write .roo/rules/raigal.md");
|
|
19021
19138
|
return {
|
|
19022
19139
|
wrote: [...cline.wrote, ...roo.wrote],
|
|
19023
19140
|
skipped: [...cline.skipped, ...roo.skipped],
|
|
@@ -19068,7 +19185,8 @@ const resolveCursorPaths = (opts) => {
|
|
|
19068
19185
|
const root = opts.scope === "project" ? path.join(opts.cwd, ".cursor") : path.join(opts.home, ".cursor");
|
|
19069
19186
|
return {
|
|
19070
19187
|
hooks: path.join(root, "hooks.json"),
|
|
19071
|
-
rules: path.join(opts.cwd, ".cursor", "rules", "
|
|
19188
|
+
rules: path.join(opts.cwd, ".cursor", "rules", "raigal.mdc"),
|
|
19189
|
+
legacyRules: path.join(opts.cwd, ".cursor", "rules", "aislop.mdc")
|
|
19072
19190
|
};
|
|
19073
19191
|
};
|
|
19074
19192
|
const buildHookEntry = () => {
|
|
@@ -19080,7 +19198,7 @@ const buildHookEntry = () => {
|
|
|
19080
19198
|
command: "raigal hook cursor",
|
|
19081
19199
|
type: "command",
|
|
19082
19200
|
timeout: 5e3,
|
|
19083
|
-
[
|
|
19201
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
19084
19202
|
v: 1,
|
|
19085
19203
|
managed: true,
|
|
19086
19204
|
hash: sentinelHash(hashBody)
|
|
@@ -19104,8 +19222,14 @@ const installCursor = (opts) => {
|
|
|
19104
19222
|
const nextHooks = renderHooksJson(readIfExists(paths.hooks));
|
|
19105
19223
|
applyContent(result, opts, paths.hooks, nextHooks, "register afterFileEdit hook");
|
|
19106
19224
|
if (opts.scope === "project") {
|
|
19107
|
-
const
|
|
19108
|
-
|
|
19225
|
+
const body = buildRaigalMdBody("cursor");
|
|
19226
|
+
const rules = upsertMarkdownFence(readIfExists(paths.rules) ?? (paths.legacyRules ? readIfExists(paths.legacyRules) : null), body, sentinelHash(body)).nextContent;
|
|
19227
|
+
applyContent(result, opts, paths.rules, rules, "write .cursor/rules/raigal.mdc");
|
|
19228
|
+
if (paths.legacyRules && readIfExists(paths.legacyRules) != null) {
|
|
19229
|
+
if (!opts.dryRun) try {
|
|
19230
|
+
fs.rmSync(paths.legacyRules, { force: true });
|
|
19231
|
+
} catch {}
|
|
19232
|
+
}
|
|
19109
19233
|
}
|
|
19110
19234
|
return result;
|
|
19111
19235
|
};
|
|
@@ -19129,7 +19253,18 @@ const uninstallCursor = (opts) => {
|
|
|
19129
19253
|
if (!stillHasHooks && otherKeys.length === 0) applyRemoval(result, opts, paths.hooks, null);
|
|
19130
19254
|
else applyRemoval(result, opts, paths.hooks, `${JSON.stringify(stripped, null, 2)}\n`);
|
|
19131
19255
|
} else result.skipped.push(paths.hooks);
|
|
19132
|
-
if (opts.scope === "project")
|
|
19256
|
+
if (opts.scope === "project") {
|
|
19257
|
+
let removedRule = false;
|
|
19258
|
+
if (readIfExists(paths.rules) != null) {
|
|
19259
|
+
applyRemoval(result, opts, paths.rules, null);
|
|
19260
|
+
removedRule = true;
|
|
19261
|
+
}
|
|
19262
|
+
if (paths.legacyRules && readIfExists(paths.legacyRules) != null) {
|
|
19263
|
+
applyRemoval(result, opts, paths.legacyRules, null);
|
|
19264
|
+
removedRule = true;
|
|
19265
|
+
}
|
|
19266
|
+
if (!removedRule) result.skipped.push(paths.rules);
|
|
19267
|
+
}
|
|
19133
19268
|
return result;
|
|
19134
19269
|
};
|
|
19135
19270
|
//#endregion
|
|
@@ -19138,6 +19273,7 @@ const resolveGeminiPaths = (opts) => {
|
|
|
19138
19273
|
const root = opts.scope === "project" ? path.join(opts.cwd, ".gemini") : path.join(opts.home, ".gemini");
|
|
19139
19274
|
return {
|
|
19140
19275
|
settings: path.join(root, "settings.json"),
|
|
19276
|
+
raigalMd: path.join(root, "RAIGAL.md"),
|
|
19141
19277
|
aislopMd: path.join(root, "AISLOP.md"),
|
|
19142
19278
|
geminiMd: path.join(root, "GEMINI.md")
|
|
19143
19279
|
};
|
|
@@ -19154,7 +19290,7 @@ const buildHookGroup = () => {
|
|
|
19154
19290
|
type: "command",
|
|
19155
19291
|
command: "raigal hook gemini",
|
|
19156
19292
|
timeout: 5e3,
|
|
19157
|
-
[
|
|
19293
|
+
[RAIGAL_SENTINEL_KEY]: {
|
|
19158
19294
|
v: 1,
|
|
19159
19295
|
managed: true,
|
|
19160
19296
|
hash: sentinelHash(hashBody)
|
|
@@ -19177,14 +19313,25 @@ const installGemini = (opts) => {
|
|
|
19177
19313
|
const result = emptyResult();
|
|
19178
19314
|
const next = renderSettings(readIfExists(paths.settings));
|
|
19179
19315
|
applyContent(result, opts, paths.settings, next, "register AfterTool hook");
|
|
19180
|
-
const
|
|
19181
|
-
|
|
19316
|
+
const body = buildRaigalMdBody("gemini");
|
|
19317
|
+
const hash = sentinelHash(body);
|
|
19318
|
+
const fenced = upsertMarkdownFence(readIfExists(paths.raigalMd) ?? readIfExists(paths.aislopMd), body, hash).nextContent;
|
|
19319
|
+
applyContent(result, opts, paths.raigalMd, fenced, "write RAIGAL.md rules");
|
|
19320
|
+
if (paths.raigalMd !== paths.aislopMd && readIfExists(paths.aislopMd) != null) {
|
|
19321
|
+
if (!opts.dryRun) try {
|
|
19322
|
+
fs.rmSync(paths.aislopMd, { force: true });
|
|
19323
|
+
} catch {}
|
|
19324
|
+
}
|
|
19182
19325
|
const existingGeminiMd = readIfExists(paths.geminiMd) ?? "";
|
|
19183
|
-
const marker = "@
|
|
19184
|
-
|
|
19326
|
+
const marker = "@RAIGAL.md";
|
|
19327
|
+
const legacyMarker = "@AISLOP.md";
|
|
19328
|
+
if (existingGeminiMd.includes(legacyMarker)) {
|
|
19329
|
+
const migrated = existingGeminiMd.split("\n").map((l) => l.trim() === legacyMarker ? marker : l).join("\n");
|
|
19330
|
+
applyContent(result, opts, paths.geminiMd, migrated, "update @RAIGAL.md reference");
|
|
19331
|
+
} else if (!existingGeminiMd.includes(marker)) {
|
|
19185
19332
|
const joiner = existingGeminiMd.endsWith("\n") || existingGeminiMd.length === 0 ? "" : "\n";
|
|
19186
19333
|
const prefix = existingGeminiMd.length === 0 ? "" : `${existingGeminiMd}${joiner}\n`;
|
|
19187
|
-
applyContent(result, opts, paths.geminiMd, `${prefix}${marker}\n`, "append @
|
|
19334
|
+
applyContent(result, opts, paths.geminiMd, `${prefix}${marker}\n`, "append @RAIGAL.md reference");
|
|
19188
19335
|
} else result.skipped.push(paths.geminiMd);
|
|
19189
19336
|
return result;
|
|
19190
19337
|
};
|
|
@@ -19208,28 +19355,39 @@ const uninstallGemini = (opts) => {
|
|
|
19208
19355
|
if (!stillHasHooks && otherKeys.length === 0) applyRemoval(result, opts, paths.settings, null);
|
|
19209
19356
|
else applyRemoval(result, opts, paths.settings, `${JSON.stringify(stripped, null, 2)}\n`);
|
|
19210
19357
|
} else result.skipped.push(paths.settings);
|
|
19211
|
-
|
|
19212
|
-
|
|
19358
|
+
let removedMd = false;
|
|
19359
|
+
if (readIfExists(paths.raigalMd) != null) {
|
|
19360
|
+
applyRemoval(result, opts, paths.raigalMd, null);
|
|
19361
|
+
removedMd = true;
|
|
19362
|
+
}
|
|
19363
|
+
if (readIfExists(paths.aislopMd) != null) {
|
|
19364
|
+
applyRemoval(result, opts, paths.aislopMd, null);
|
|
19365
|
+
removedMd = true;
|
|
19366
|
+
}
|
|
19367
|
+
if (!removedMd) result.skipped.push(paths.raigalMd);
|
|
19213
19368
|
const geminiMd = readIfExists(paths.geminiMd);
|
|
19214
|
-
if (geminiMd?.includes("@AISLOP.md")) {
|
|
19215
|
-
const stripped = geminiMd.split("\n").filter((l) => l.trim() !== "@AISLOP.md").join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
19369
|
+
if (geminiMd?.includes("@RAIGAL.md") || geminiMd?.includes("@AISLOP.md")) {
|
|
19370
|
+
const stripped = geminiMd.split("\n").filter((l) => l.trim() !== "@RAIGAL.md" && l.trim() !== "@AISLOP.md").join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
19216
19371
|
applyRemoval(result, opts, paths.geminiMd, stripped.length === 0 ? null : `${stripped}\n`);
|
|
19217
19372
|
} else result.skipped.push(paths.geminiMd);
|
|
19218
19373
|
return result;
|
|
19219
19374
|
};
|
|
19220
19375
|
//#endregion
|
|
19221
19376
|
//#region src/hooks/install/kilocode.ts
|
|
19222
|
-
const resolveKilocodePaths = (opts) => ({
|
|
19377
|
+
const resolveKilocodePaths = (opts) => ({
|
|
19378
|
+
rules: path.join(opts.cwd, ".kilocode", "rules", "raigal-rules.md"),
|
|
19379
|
+
legacyRules: path.join(opts.cwd, ".kilocode", "rules", "aislop-rules.md")
|
|
19380
|
+
});
|
|
19223
19381
|
const installKilocode = (opts) => {
|
|
19224
19382
|
if (opts.scope !== "project") return {
|
|
19225
19383
|
wrote: [],
|
|
19226
19384
|
skipped: [],
|
|
19227
19385
|
planned: [{
|
|
19228
|
-
path: ".kilocode/rules/
|
|
19386
|
+
path: ".kilocode/rules/raigal-rules.md",
|
|
19229
19387
|
summary: "Kilo Code is project-scope only; pass --project"
|
|
19230
19388
|
}]
|
|
19231
19389
|
};
|
|
19232
|
-
return installRulesOnly(opts, resolveKilocodePaths(opts), "write .kilocode/rules/
|
|
19390
|
+
return installRulesOnly(opts, resolveKilocodePaths(opts), "write .kilocode/rules/raigal-rules.md");
|
|
19233
19391
|
};
|
|
19234
19392
|
const uninstallKilocode = (opts) => {
|
|
19235
19393
|
if (opts.scope !== "project") return {
|
|
@@ -19383,18 +19541,24 @@ const paths = {
|
|
|
19383
19541
|
const p = resolveClaudePaths(opts);
|
|
19384
19542
|
return [
|
|
19385
19543
|
p.settings,
|
|
19544
|
+
p.raigalMd,
|
|
19386
19545
|
p.aislopMd,
|
|
19387
19546
|
p.claudeMd
|
|
19388
19547
|
];
|
|
19389
19548
|
},
|
|
19390
19549
|
cursor: (opts) => {
|
|
19391
19550
|
const p = resolveCursorPaths(opts);
|
|
19392
|
-
return opts.scope === "project" ? [
|
|
19551
|
+
return opts.scope === "project" ? [
|
|
19552
|
+
p.hooks,
|
|
19553
|
+
p.rules,
|
|
19554
|
+
...p.legacyRules ? [p.legacyRules] : []
|
|
19555
|
+
] : [p.hooks];
|
|
19393
19556
|
},
|
|
19394
19557
|
gemini: (opts) => {
|
|
19395
19558
|
const p = resolveGeminiPaths(opts);
|
|
19396
19559
|
return [
|
|
19397
19560
|
p.settings,
|
|
19561
|
+
p.raigalMd,
|
|
19398
19562
|
p.aislopMd,
|
|
19399
19563
|
p.geminiMd
|
|
19400
19564
|
];
|
|
@@ -19402,9 +19566,22 @@ const paths = {
|
|
|
19402
19566
|
pi: (opts) => [resolvePiPaths(opts).extension],
|
|
19403
19567
|
codex: (opts) => [resolveCodexPaths(opts).rules],
|
|
19404
19568
|
windsurf: (opts) => [resolveWindsurfPaths(opts).rules],
|
|
19405
|
-
cline: (opts) =>
|
|
19406
|
-
|
|
19407
|
-
|
|
19569
|
+
cline: (opts) => {
|
|
19570
|
+
const roo = resolveRooPaths(opts);
|
|
19571
|
+
return [
|
|
19572
|
+
resolveClinePaths(opts).rules,
|
|
19573
|
+
roo.rules,
|
|
19574
|
+
...roo.legacyRules ? [roo.legacyRules] : []
|
|
19575
|
+
];
|
|
19576
|
+
},
|
|
19577
|
+
kilocode: (opts) => {
|
|
19578
|
+
const k = resolveKilocodePaths(opts);
|
|
19579
|
+
return [k.rules, ...k.legacyRules ? [k.legacyRules] : []];
|
|
19580
|
+
},
|
|
19581
|
+
antigravity: (opts) => {
|
|
19582
|
+
const a = resolveAntigravityPaths(opts);
|
|
19583
|
+
return [a.rules, ...a.legacyRules ? [a.legacyRules] : []];
|
|
19584
|
+
},
|
|
19408
19585
|
copilot: (opts) => [resolveCopilotPaths(opts).rules]
|
|
19409
19586
|
};
|
|
19410
19587
|
const REGISTRY = {
|
|
@@ -20486,12 +20663,12 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
20486
20663
|
scoreFileCount
|
|
20487
20664
|
});
|
|
20488
20665
|
if (options.sarif) {
|
|
20489
|
-
const { buildSarifLog } = await import("./sarif-
|
|
20666
|
+
const { buildSarifLog } = await import("./sarif-Cu7Rm1Ma.js");
|
|
20490
20667
|
console.log(JSON.stringify(buildSarifLog(results), null, 2));
|
|
20491
20668
|
return completion;
|
|
20492
20669
|
}
|
|
20493
20670
|
if (options.json) {
|
|
20494
|
-
const { buildJsonOutput } = await import("./json-
|
|
20671
|
+
const { buildJsonOutput } = await import("./json-DxIA7CCt.js");
|
|
20495
20672
|
const jsonOut = buildJsonOutput(results, scoreResult, scoreFileCount, elapsedMs, scanCoverage);
|
|
20496
20673
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
20497
20674
|
return completion;
|
|
@@ -20810,10 +20987,12 @@ const buildInitSuccessRender = (input) => {
|
|
|
20810
20987
|
}, deps);
|
|
20811
20988
|
const writtenCount = input.steps.filter((s) => s.status === "done").length;
|
|
20812
20989
|
const footer = writtenCount === 0 ? "Nothing to write" : `Done · wrote ${writtenCount} file${writtenCount === 1 ? "" : "s"}`;
|
|
20813
|
-
|
|
20990
|
+
const rail = renderRail({
|
|
20814
20991
|
steps: input.steps,
|
|
20815
20992
|
footer
|
|
20816
|
-
}, deps)
|
|
20993
|
+
}, deps);
|
|
20994
|
+
const extraHint = input.hint ? `\n${renderHintLine(input.hint, deps)}` : "";
|
|
20995
|
+
return `${header}${rail}\n${renderHintLine(`Try ${input.nextCommand}`, deps)}${extraHint}`;
|
|
20817
20996
|
};
|
|
20818
20997
|
const ENGINE_CHOICES = [
|
|
20819
20998
|
{
|
|
@@ -20864,7 +21043,45 @@ const writeGithubWorkflow = (rootDirectory, enabled) => {
|
|
|
20864
21043
|
relativePath
|
|
20865
21044
|
};
|
|
20866
21045
|
};
|
|
20867
|
-
const
|
|
21046
|
+
const writeGitlabWorkflow = (rootDirectory, enabled) => {
|
|
21047
|
+
const relativePath = DEFAULT_GITLAB_CI_FILE;
|
|
21048
|
+
if (!enabled) return { status: "declined" };
|
|
21049
|
+
const workflowPath = path.join(rootDirectory, DEFAULT_GITLAB_CI_FILE);
|
|
21050
|
+
if (fs.existsSync(workflowPath)) return {
|
|
21051
|
+
status: "skipped-exists",
|
|
21052
|
+
relativePath
|
|
21053
|
+
};
|
|
21054
|
+
fs.writeFileSync(workflowPath, DEFAULT_GITLAB_CI_YAML);
|
|
21055
|
+
return {
|
|
21056
|
+
status: "written",
|
|
21057
|
+
relativePath
|
|
21058
|
+
};
|
|
21059
|
+
};
|
|
21060
|
+
const writeBitbucketWorkflow = (rootDirectory, enabled) => {
|
|
21061
|
+
const relativePath = DEFAULT_BITBUCKET_FILE;
|
|
21062
|
+
if (!enabled) return { status: "declined" };
|
|
21063
|
+
const workflowPath = path.join(rootDirectory, DEFAULT_BITBUCKET_FILE);
|
|
21064
|
+
if (fs.existsSync(workflowPath)) return {
|
|
21065
|
+
status: "skipped-exists",
|
|
21066
|
+
relativePath
|
|
21067
|
+
};
|
|
21068
|
+
fs.writeFileSync(workflowPath, DEFAULT_BITBUCKET_PIPELINES_YAML);
|
|
21069
|
+
return {
|
|
21070
|
+
status: "written",
|
|
21071
|
+
relativePath
|
|
21072
|
+
};
|
|
21073
|
+
};
|
|
21074
|
+
const detectDefaultCiProvider = (directory) => {
|
|
21075
|
+
if (fs.existsSync(path.join(directory, ".gitlab-ci.yml"))) return "gitlab";
|
|
21076
|
+
if (fs.existsSync(path.join(directory, "bitbucket-pipelines.yml"))) return "bitbucket";
|
|
21077
|
+
if (fs.existsSync(path.join(directory, ".github/workflows"))) return "github";
|
|
21078
|
+
const detectedOwner = detectLocalRepoOwner(directory);
|
|
21079
|
+
if (detectedOwner.hasRemote) {
|
|
21080
|
+
if (detectedOwner.isGitHubRemote) return "github";
|
|
21081
|
+
}
|
|
21082
|
+
return "github";
|
|
21083
|
+
};
|
|
21084
|
+
const promptForConfigChoices = async (directory) => {
|
|
20868
21085
|
const enginesSelection = await runCancellable(() => multiselect({
|
|
20869
21086
|
message: "Which engines should run?",
|
|
20870
21087
|
options: ENGINE_CHOICES,
|
|
@@ -20894,15 +21111,26 @@ const promptForConfigChoices = async () => {
|
|
|
20894
21111
|
});
|
|
20895
21112
|
if (isCancel(telemetryChoice)) return null;
|
|
20896
21113
|
const workflowChoice = await select({
|
|
20897
|
-
message: "Add
|
|
20898
|
-
options: [
|
|
20899
|
-
|
|
20900
|
-
|
|
20901
|
-
|
|
20902
|
-
|
|
20903
|
-
|
|
20904
|
-
|
|
20905
|
-
|
|
21114
|
+
message: "Add automated CI quality gate workflow?",
|
|
21115
|
+
options: [
|
|
21116
|
+
{
|
|
21117
|
+
value: "github",
|
|
21118
|
+
label: "GitHub Actions — writes .github/workflows/raigal.yml"
|
|
21119
|
+
},
|
|
21120
|
+
{
|
|
21121
|
+
value: "gitlab",
|
|
21122
|
+
label: "GitLab CI — writes .gitlab-ci.yml"
|
|
21123
|
+
},
|
|
21124
|
+
{
|
|
21125
|
+
value: "bitbucket",
|
|
21126
|
+
label: "Bitbucket Pipelines — writes bitbucket-pipelines.yml"
|
|
21127
|
+
},
|
|
21128
|
+
{
|
|
21129
|
+
value: "none",
|
|
21130
|
+
label: "No, I'll wire CI myself"
|
|
21131
|
+
}
|
|
21132
|
+
],
|
|
21133
|
+
initialValue: detectDefaultCiProvider(directory)
|
|
20906
21134
|
});
|
|
20907
21135
|
if (isCancel(workflowChoice)) return null;
|
|
20908
21136
|
return {
|
|
@@ -20910,7 +21138,8 @@ const promptForConfigChoices = async () => {
|
|
|
20910
21138
|
failBelow: Number(failBelowRaw),
|
|
20911
21139
|
typecheck: DEFAULT_CONFIG.lint.typecheck,
|
|
20912
21140
|
telemetryEnabled: telemetryChoice === "enabled",
|
|
20913
|
-
writeGithubWorkflow: workflowChoice === "
|
|
21141
|
+
writeGithubWorkflow: workflowChoice === "github",
|
|
21142
|
+
ciProvider: workflowChoice
|
|
20914
21143
|
};
|
|
20915
21144
|
};
|
|
20916
21145
|
const strictChoices = () => ({
|
|
@@ -20918,7 +21147,8 @@ const strictChoices = () => ({
|
|
|
20918
21147
|
failBelow: 85,
|
|
20919
21148
|
typecheck: true,
|
|
20920
21149
|
telemetryEnabled: DEFAULT_CONFIG.telemetry.enabled,
|
|
20921
|
-
writeGithubWorkflow: true
|
|
21150
|
+
writeGithubWorkflow: true,
|
|
21151
|
+
ciProvider: "github"
|
|
20922
21152
|
});
|
|
20923
21153
|
const writeAislopConfig = (configDir, configPath, choices) => {
|
|
20924
21154
|
const selected = new Set(choices.engines);
|
|
@@ -20995,7 +21225,7 @@ const initCommand = async (directory, options = {}) => {
|
|
|
20995
21225
|
return;
|
|
20996
21226
|
}
|
|
20997
21227
|
}
|
|
20998
|
-
const choices = options.strict ? strictChoices() : await promptForConfigChoices();
|
|
21228
|
+
const choices = options.strict ? strictChoices() : await promptForConfigChoices(resolvedDir);
|
|
20999
21229
|
if (!choices) return;
|
|
21000
21230
|
writeAislopConfig(configDir, configPath, choices);
|
|
21001
21231
|
const steps = [{
|
|
@@ -21012,19 +21242,49 @@ const initCommand = async (directory, options = {}) => {
|
|
|
21012
21242
|
status: "skipped",
|
|
21013
21243
|
label: `${CONFIG_DIR}/${RULES_FILE} already exists`
|
|
21014
21244
|
});
|
|
21015
|
-
|
|
21016
|
-
if (
|
|
21017
|
-
|
|
21018
|
-
|
|
21019
|
-
|
|
21020
|
-
|
|
21021
|
-
|
|
21022
|
-
|
|
21023
|
-
|
|
21245
|
+
let ciHint;
|
|
21246
|
+
if (choices.ciProvider === "gitlab") {
|
|
21247
|
+
const gitlabResult = writeGitlabWorkflow(resolvedDir, true);
|
|
21248
|
+
if (gitlabResult.status === "written") {
|
|
21249
|
+
steps.push({
|
|
21250
|
+
status: "done",
|
|
21251
|
+
label: `Wrote ${gitlabResult.relativePath}`
|
|
21252
|
+
});
|
|
21253
|
+
ciHint = "Set RAIGAL_TOKEN in GitLab CI/CD Variables (Settings > CI/CD > Variables).";
|
|
21254
|
+
} else if (gitlabResult.status === "skipped-exists") steps.push({
|
|
21255
|
+
status: "skipped",
|
|
21256
|
+
label: `${gitlabResult.relativePath} already exists`
|
|
21257
|
+
});
|
|
21258
|
+
} else if (choices.ciProvider === "bitbucket") {
|
|
21259
|
+
const bbResult = writeBitbucketWorkflow(resolvedDir, true);
|
|
21260
|
+
if (bbResult.status === "written") {
|
|
21261
|
+
steps.push({
|
|
21262
|
+
status: "done",
|
|
21263
|
+
label: `Wrote ${bbResult.relativePath}`
|
|
21264
|
+
});
|
|
21265
|
+
ciHint = "Set RAIGAL_TOKEN in Bitbucket Repository Variables (Repository settings > Repository variables).";
|
|
21266
|
+
} else if (bbResult.status === "skipped-exists") steps.push({
|
|
21267
|
+
status: "skipped",
|
|
21268
|
+
label: `${bbResult.relativePath} already exists`
|
|
21269
|
+
});
|
|
21270
|
+
} else if (choices.writeGithubWorkflow || choices.ciProvider === "github") {
|
|
21271
|
+
const workflowResult = writeGithubWorkflow(resolvedDir, true);
|
|
21272
|
+
if (workflowResult.status === "written") {
|
|
21273
|
+
steps.push({
|
|
21274
|
+
status: "done",
|
|
21275
|
+
label: `Wrote ${workflowResult.relativePath}`
|
|
21276
|
+
});
|
|
21277
|
+
ciHint = "GitHub Actions zero-token OIDC enabled (permissions: id-token: write).";
|
|
21278
|
+
} else if (workflowResult.status === "skipped-exists") steps.push({
|
|
21279
|
+
status: "skipped",
|
|
21280
|
+
label: `${workflowResult.relativePath} already exists`
|
|
21281
|
+
});
|
|
21282
|
+
}
|
|
21024
21283
|
process.stdout.write(buildInitSuccessRender({
|
|
21025
21284
|
steps,
|
|
21026
21285
|
nextCommand: `${invocation} scan`,
|
|
21027
|
-
includeHeader: false
|
|
21286
|
+
includeHeader: false,
|
|
21287
|
+
hint: ciHint
|
|
21028
21288
|
}));
|
|
21029
21289
|
};
|
|
21030
21290
|
//#endregion
|