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 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. Maximum 100 rules per file. IRA rules are for nuanced, context-dependent standards that linters cannot express. Deterministic checks (naming conventions, import order, formatting) belong in ESLint.
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: Review Current PR`
283
- 4. Enter your PR number
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, that is all you need. No API keys, no configuration. Alternatively, set the AI provider to `amp` if you have the AMP CLI installed (`amp login`).
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
- - Maximum 100 rules. Deterministic checks (naming, formatting) belong in ESLint.
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
- - Maximum 100 rules. Deterministic checks (naming, formatting) belong in ESLint.
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 MAX_RULES = 100;
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 > MAX_RULES) {
573
- console.warn(`IRA: .ira-rules.json has more than ${MAX_RULES} rules. Only the first ${MAX_RULES} will be enforced. Tip: Move deterministic rules to ESLint and keep only nuanced, context-dependent rules in IRA.`);
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 MAX_RULES = 100;
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 > MAX_RULES) {
807
- console.warn(`IRA: .ira-rules.json has more than ${MAX_RULES} rules. Only the first ${MAX_RULES} will be enforced. Tip: Move deterministic rules to ESLint and keep only nuanced, context-dependent rules in IRA.`);
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 });