ira-review 3.0.0 → 3.0.2
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.github.md +4 -4
- package/README.md +1 -1
- package/README.npm.md +1 -1
- package/dist/cli.js +50 -5
- package/dist/index.cjs +50 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +50 -5
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/README.github.md
CHANGED
|
@@ -182,7 +182,7 @@ Each rule has a `message` (what to tell the developer), a `severity` (BLOCKER, C
|
|
|
182
182
|
}
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
Rules without `paths` apply to all files. Rules with `paths` are only checked against matching files. The file is validated at load time: invalid severity values and missing required fields are skipped with a warning.
|
|
185
|
+
Rules without `paths` apply to all files. Rules with `paths` are only checked against matching files. The file is validated at load time: invalid severity values and missing required fields are skipped with a warning. There is no hard cap on the number of rules; a soft warning is logged above 500 since large rulesets can inflate the AI prompt. IRA rules are for nuanced, context-dependent standards that linters cannot express. Deterministic checks (naming conventions, import order, formatting) belong in ESLint.
|
|
186
186
|
|
|
187
187
|
Rules are enforced in all review surfaces (CLI, CI/CD, VS Code extension) with no license gating. In the VS Code extension, run `IRA: Init Rules File` from the command palette to scaffold an empty `.ira-rules.json`. The extension ships a JSON Schema for the file, so you get autocomplete and validation as you edit.
|
|
188
188
|
|
|
@@ -279,10 +279,10 @@ Suggested Fix: Use parameterized queries:
|
|
|
279
279
|
|
|
280
280
|
1. Install from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=ira-review.ira-review-vscode)
|
|
281
281
|
2. Open a project with a GitHub or Bitbucket remote
|
|
282
|
-
3. `Cmd+Shift+P` > `IRA:
|
|
283
|
-
4.
|
|
282
|
+
3. `Cmd+Shift+P` > `IRA: Quick Start` (auto-detects SCM, walks you through tokens, optionally sets up JIRA)
|
|
283
|
+
4. `Cmd+Shift+P` > `IRA: Review Current PR` and enter your PR number
|
|
284
284
|
|
|
285
|
-
If you have GitHub Copilot,
|
|
285
|
+
If you have GitHub Copilot, Quick Start finishes in seconds — no API keys, no configuration. Alternatively, set the AI provider to `amp` if you have the AMP CLI installed (`amp login`). For Bitbucket Server / Data Center or JIRA, Quick Start gives you the right token-creation links inline.
|
|
286
286
|
|
|
287
287
|
### CLI
|
|
288
288
|
|
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ Commit a `.ira-rules.json` to your repo root. Rules are injected into the AI pro
|
|
|
78
78
|
**Rules:**
|
|
79
79
|
- `message` + `severity` required. `bad`/`good` examples and `paths` are optional.
|
|
80
80
|
- Rules without `paths` apply to all files. Rules with `paths` match only those directories.
|
|
81
|
-
-
|
|
81
|
+
- No hard cap on rules (soft warning above 500). Deterministic checks (naming, formatting) belong in ESLint.
|
|
82
82
|
- Invalid rules are skipped with a warning, not a crash.
|
|
83
83
|
- No license gating. Works in CLI, CI/CD, and VS Code extension.
|
|
84
84
|
|
package/README.npm.md
CHANGED
|
@@ -78,7 +78,7 @@ Commit a `.ira-rules.json` to your repo root. Rules are injected into the AI pro
|
|
|
78
78
|
**Rules:**
|
|
79
79
|
- `message` + `severity` required. `bad`/`good` examples and `paths` are optional.
|
|
80
80
|
- Rules without `paths` apply to all files. Rules with `paths` match only those directories.
|
|
81
|
-
-
|
|
81
|
+
- No hard cap on rules (soft warning above 500). Deterministic checks (naming, formatting) belong in ESLint.
|
|
82
82
|
- Invalid rules are skipped with a warning, not a crash.
|
|
83
83
|
- No license gating. Works in CLI, CI/CD, and VS Code extension.
|
|
84
84
|
|
package/dist/cli.js
CHANGED
|
@@ -513,7 +513,7 @@ function annotateDiffWithLineNumbers(diff) {
|
|
|
513
513
|
import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
|
|
514
514
|
import { resolve } from "path";
|
|
515
515
|
var VALID_SEVERITIES = ["BLOCKER", "CRITICAL", "MAJOR", "MINOR"];
|
|
516
|
-
var
|
|
516
|
+
var RULES_SOFT_WARN_THRESHOLD = 500;
|
|
517
517
|
function loadRawRulesFile(cwd) {
|
|
518
518
|
const dir = cwd ?? process.cwd();
|
|
519
519
|
const filePath = resolve(dir, ".ira-rules.json");
|
|
@@ -569,9 +569,8 @@ function loadRulesFile(cwd) {
|
|
|
569
569
|
...typeof rule.createdAt === "string" && { createdAt: rule.createdAt }
|
|
570
570
|
});
|
|
571
571
|
}
|
|
572
|
-
if (valid.length >
|
|
573
|
-
console.warn(`IRA: .ira-rules.json has
|
|
574
|
-
return valid.slice(0, MAX_RULES);
|
|
572
|
+
if (valid.length > RULES_SOFT_WARN_THRESHOLD) {
|
|
573
|
+
console.warn(`IRA: .ira-rules.json has ${valid.length} rules (>${RULES_SOFT_WARN_THRESHOLD}). All will be enforced, but large rulesets can inflate the AI prompt and risk hitting model context limits. Tip: Move deterministic rules to ESLint and keep only nuanced, context-dependent rules in IRA.`);
|
|
575
574
|
}
|
|
576
575
|
return valid;
|
|
577
576
|
}
|
|
@@ -1006,12 +1005,15 @@ var CommentTracker = class {
|
|
|
1006
1005
|
provider;
|
|
1007
1006
|
baseUrl;
|
|
1008
1007
|
headers;
|
|
1009
|
-
// Bitbucket
|
|
1008
|
+
// Bitbucket Cloud
|
|
1010
1009
|
workspace;
|
|
1011
1010
|
repoSlug;
|
|
1012
1011
|
// GitHub
|
|
1013
1012
|
owner;
|
|
1014
1013
|
repo;
|
|
1014
|
+
// Bitbucket Server
|
|
1015
|
+
project;
|
|
1016
|
+
bbServerRepoSlug;
|
|
1015
1017
|
constructor(config, provider = "bitbucket") {
|
|
1016
1018
|
this.provider = provider;
|
|
1017
1019
|
if (provider === "github") {
|
|
@@ -1024,6 +1026,15 @@ var CommentTracker = class {
|
|
|
1024
1026
|
Accept: "application/vnd.github.v3+json",
|
|
1025
1027
|
"Content-Type": "application/json"
|
|
1026
1028
|
};
|
|
1029
|
+
} else if (provider === "bitbucket-server") {
|
|
1030
|
+
const bbs = config;
|
|
1031
|
+
this.baseUrl = bbs.baseUrl.replace(/\/+$/, "");
|
|
1032
|
+
this.project = bbs.project;
|
|
1033
|
+
this.bbServerRepoSlug = bbs.repoSlug;
|
|
1034
|
+
this.headers = {
|
|
1035
|
+
Authorization: `Bearer ${bbs.token}`,
|
|
1036
|
+
"Content-Type": "application/json"
|
|
1037
|
+
};
|
|
1027
1038
|
} else {
|
|
1028
1039
|
const bb = config;
|
|
1029
1040
|
this.baseUrl = (bb.baseUrl ?? "https://api.bitbucket.org/2.0").replace(/\/+$/, "");
|
|
@@ -1039,6 +1050,9 @@ var CommentTracker = class {
|
|
|
1039
1050
|
if (this.provider === "github") {
|
|
1040
1051
|
return this.getGitHubIraComments(pullRequestId);
|
|
1041
1052
|
}
|
|
1053
|
+
if (this.provider === "bitbucket-server") {
|
|
1054
|
+
return this.getBitbucketServerIraComments(pullRequestId);
|
|
1055
|
+
}
|
|
1042
1056
|
return this.getBitbucketIraComments(pullRequestId);
|
|
1043
1057
|
}
|
|
1044
1058
|
async getBitbucketIraComments(pullRequestId) {
|
|
@@ -1098,6 +1112,37 @@ var CommentTracker = class {
|
|
|
1098
1112
|
}
|
|
1099
1113
|
return keys;
|
|
1100
1114
|
}
|
|
1115
|
+
async getBitbucketServerIraComments(pullRequestId) {
|
|
1116
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1117
|
+
let start = 0;
|
|
1118
|
+
while (true) {
|
|
1119
|
+
const url = `${this.baseUrl}/rest/api/1.0/projects/${this.project}/repos/${this.bbServerRepoSlug}/pull-requests/${pullRequestId}/comments?start=${start}&limit=100`;
|
|
1120
|
+
const data = await this.fetchBitbucketServerPage(url);
|
|
1121
|
+
for (const comment of data.values) {
|
|
1122
|
+
if (!comment.text.includes(IRA_MARKER)) continue;
|
|
1123
|
+
const meta = comment.text.match(IRA_META_RE);
|
|
1124
|
+
if (meta) {
|
|
1125
|
+
keys.add(`${meta[1]}:${meta[2]}:${meta[3]}`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (data.isLastPage) break;
|
|
1129
|
+
start = data.nextPageStart ?? start + 100;
|
|
1130
|
+
}
|
|
1131
|
+
return keys;
|
|
1132
|
+
}
|
|
1133
|
+
async fetchBitbucketServerPage(url) {
|
|
1134
|
+
return withRetry(async () => {
|
|
1135
|
+
const response = await fetchWithTimeout(url, { headers: this.headers });
|
|
1136
|
+
if (!response.ok) {
|
|
1137
|
+
const body = await response.text();
|
|
1138
|
+
throw new RetryableError(
|
|
1139
|
+
parseApiError(response.status, body, "Bitbucket Server"),
|
|
1140
|
+
response.status
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
return await response.json();
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1101
1146
|
async fetchBitbucketPage(url) {
|
|
1102
1147
|
return withRetry(async () => {
|
|
1103
1148
|
const response = await fetchWithTimeout(url, { headers: this.headers });
|
package/dist/index.cjs
CHANGED
|
@@ -747,7 +747,7 @@ function annotateDiffWithLineNumbers(diff) {
|
|
|
747
747
|
var import_node_fs6 = require("fs");
|
|
748
748
|
var import_node_path6 = require("path");
|
|
749
749
|
var VALID_SEVERITIES = ["BLOCKER", "CRITICAL", "MAJOR", "MINOR"];
|
|
750
|
-
var
|
|
750
|
+
var RULES_SOFT_WARN_THRESHOLD = 500;
|
|
751
751
|
function loadRawRulesFile(cwd) {
|
|
752
752
|
const dir = cwd ?? process.cwd();
|
|
753
753
|
const filePath = (0, import_node_path6.resolve)(dir, ".ira-rules.json");
|
|
@@ -803,9 +803,8 @@ function loadRulesFile(cwd) {
|
|
|
803
803
|
...typeof rule.createdAt === "string" && { createdAt: rule.createdAt }
|
|
804
804
|
});
|
|
805
805
|
}
|
|
806
|
-
if (valid.length >
|
|
807
|
-
console.warn(`IRA: .ira-rules.json has
|
|
808
|
-
return valid.slice(0, MAX_RULES);
|
|
806
|
+
if (valid.length > RULES_SOFT_WARN_THRESHOLD) {
|
|
807
|
+
console.warn(`IRA: .ira-rules.json has ${valid.length} rules (>${RULES_SOFT_WARN_THRESHOLD}). All will be enforced, but large rulesets can inflate the AI prompt and risk hitting model context limits. Tip: Move deterministic rules to ESLint and keep only nuanced, context-dependent rules in IRA.`);
|
|
809
808
|
}
|
|
810
809
|
return valid;
|
|
811
810
|
}
|
|
@@ -1796,12 +1795,15 @@ var CommentTracker = class {
|
|
|
1796
1795
|
provider;
|
|
1797
1796
|
baseUrl;
|
|
1798
1797
|
headers;
|
|
1799
|
-
// Bitbucket
|
|
1798
|
+
// Bitbucket Cloud
|
|
1800
1799
|
workspace;
|
|
1801
1800
|
repoSlug;
|
|
1802
1801
|
// GitHub
|
|
1803
1802
|
owner;
|
|
1804
1803
|
repo;
|
|
1804
|
+
// Bitbucket Server
|
|
1805
|
+
project;
|
|
1806
|
+
bbServerRepoSlug;
|
|
1805
1807
|
constructor(config, provider = "bitbucket") {
|
|
1806
1808
|
this.provider = provider;
|
|
1807
1809
|
if (provider === "github") {
|
|
@@ -1814,6 +1816,15 @@ var CommentTracker = class {
|
|
|
1814
1816
|
Accept: "application/vnd.github.v3+json",
|
|
1815
1817
|
"Content-Type": "application/json"
|
|
1816
1818
|
};
|
|
1819
|
+
} else if (provider === "bitbucket-server") {
|
|
1820
|
+
const bbs = config;
|
|
1821
|
+
this.baseUrl = bbs.baseUrl.replace(/\/+$/, "");
|
|
1822
|
+
this.project = bbs.project;
|
|
1823
|
+
this.bbServerRepoSlug = bbs.repoSlug;
|
|
1824
|
+
this.headers = {
|
|
1825
|
+
Authorization: `Bearer ${bbs.token}`,
|
|
1826
|
+
"Content-Type": "application/json"
|
|
1827
|
+
};
|
|
1817
1828
|
} else {
|
|
1818
1829
|
const bb = config;
|
|
1819
1830
|
this.baseUrl = (bb.baseUrl ?? "https://api.bitbucket.org/2.0").replace(/\/+$/, "");
|
|
@@ -1829,6 +1840,9 @@ var CommentTracker = class {
|
|
|
1829
1840
|
if (this.provider === "github") {
|
|
1830
1841
|
return this.getGitHubIraComments(pullRequestId);
|
|
1831
1842
|
}
|
|
1843
|
+
if (this.provider === "bitbucket-server") {
|
|
1844
|
+
return this.getBitbucketServerIraComments(pullRequestId);
|
|
1845
|
+
}
|
|
1832
1846
|
return this.getBitbucketIraComments(pullRequestId);
|
|
1833
1847
|
}
|
|
1834
1848
|
async getBitbucketIraComments(pullRequestId) {
|
|
@@ -1888,6 +1902,37 @@ var CommentTracker = class {
|
|
|
1888
1902
|
}
|
|
1889
1903
|
return keys;
|
|
1890
1904
|
}
|
|
1905
|
+
async getBitbucketServerIraComments(pullRequestId) {
|
|
1906
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1907
|
+
let start = 0;
|
|
1908
|
+
while (true) {
|
|
1909
|
+
const url = `${this.baseUrl}/rest/api/1.0/projects/${this.project}/repos/${this.bbServerRepoSlug}/pull-requests/${pullRequestId}/comments?start=${start}&limit=100`;
|
|
1910
|
+
const data = await this.fetchBitbucketServerPage(url);
|
|
1911
|
+
for (const comment of data.values) {
|
|
1912
|
+
if (!comment.text.includes(IRA_MARKER)) continue;
|
|
1913
|
+
const meta = comment.text.match(IRA_META_RE);
|
|
1914
|
+
if (meta) {
|
|
1915
|
+
keys.add(`${meta[1]}:${meta[2]}:${meta[3]}`);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
if (data.isLastPage) break;
|
|
1919
|
+
start = data.nextPageStart ?? start + 100;
|
|
1920
|
+
}
|
|
1921
|
+
return keys;
|
|
1922
|
+
}
|
|
1923
|
+
async fetchBitbucketServerPage(url) {
|
|
1924
|
+
return withRetry(async () => {
|
|
1925
|
+
const response = await fetchWithTimeout(url, { headers: this.headers });
|
|
1926
|
+
if (!response.ok) {
|
|
1927
|
+
const body = await response.text();
|
|
1928
|
+
throw new RetryableError(
|
|
1929
|
+
parseApiError(response.status, body, "Bitbucket Server"),
|
|
1930
|
+
response.status
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
return await response.json();
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1891
1936
|
async fetchBitbucketPage(url) {
|
|
1892
1937
|
return withRetry(async () => {
|
|
1893
1938
|
const response = await fetchWithTimeout(url, { headers: this.headers });
|