ai-rulez 4.9.2 → 4.9.4

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.
Files changed (4) hide show
  1. package/README.md +29 -29
  2. package/ai-rulez.js +22 -22
  3. package/install.js +409 -442
  4. package/package.json +36 -36
package/README.md CHANGED
@@ -40,7 +40,7 @@ ai-rulez generates correct, tool-native output for **20 platforms**: Claude, Cur
40
40
 
41
41
  ## Generate Plugins, Not Just Config
42
42
 
43
- ai-rulez doesn't only write config into *your* repo — it also packages your project as **distributable plugins**. Run `ai-rulez generate --plugin` and the same `.ai-rulez/` source (skills, commands, agents, MCP servers) becomes installable **plugin bundles and a marketplace index** for Claude, Cursor, Codex, Gemini, Kimi, OpenCode, Factory, and Hermes Agent.
43
+ ai-rulez doesn't only write config into _your_ repo — it also packages your project as **distributable plugins**. Run `ai-rulez generate --plugin` and the same `.ai-rulez/` source (skills, commands, agents, MCP servers) becomes installable **plugin bundles and a marketplace index** for Claude, Cursor, Codex, Gemini, Kimi, OpenCode, Factory, and Hermes Agent.
44
44
 
45
45
  ```bash
46
46
  ai-rulez generate --plugin # write plugin bundles + marketplace.json
@@ -58,28 +58,28 @@ ai-rulez isn't just a config generator. It ships with **33 builtin domains** con
58
58
 
59
59
  These activate automatically. No configuration needed.
60
60
 
61
- | Domain | What it enforces |
62
- |--------|-----------------|
63
- | **ai-governance** | No AI signatures in commits. Concise communication. Systematic debugging. Verification before claiming success. Critical review of subagent output. |
64
- | **code-quality** | Anti-patterns prevention. Complexity limits. Dead code removal. Error handling standards. Readability. |
65
- | **testing** | TDD workflow (red-green-refactor, no exceptions). Testing anti-patterns. Meaningful assertions. Test independence. |
66
- | **git-workflow** | Atomic commits. Conventional commit messages. Safe operations. Branch hygiene. |
67
- | **security** | Secrets handling. Input validation. Dependency auditing. Least privilege. |
68
- | **token-efficiency** | Task runner usage. Incremental approach. Context preservation. Batch operations. |
69
- | **agent-delegation** | Multi-agent coordination and delegation patterns. |
61
+ | Domain | What it enforces |
62
+ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | **ai-governance** | No AI signatures in commits. Concise communication. Systematic debugging. Verification before claiming success. Critical review of subagent output. |
64
+ | **code-quality** | Anti-patterns prevention. Complexity limits. Dead code removal. Error handling standards. Readability. |
65
+ | **testing** | TDD workflow (red-green-refactor, no exceptions). Testing anti-patterns. Meaningful assertions. Test independence. |
66
+ | **git-workflow** | Atomic commits. Conventional commit messages. Safe operations. Branch hygiene. |
67
+ | **security** | Secrets handling. Input validation. Dependency auditing. Least privilege. |
68
+ | **token-efficiency** | Task runner usage. Incremental approach. Context preservation. Batch operations. |
69
+ | **agent-delegation** | Multi-agent coordination and delegation patterns. |
70
70
 
71
71
  ### Builtin Agents
72
72
 
73
73
  Specialized agents ready to use as subagents:
74
74
 
75
- | Agent | Domain | Model | What it does |
76
- |-------|--------|-------|-------------|
77
- | **code-reviewer** | ai-governance | sonnet | Reviews changes for correctness, security, and conventions. Reports by severity. |
78
- | **test-writer** | testing | sonnet | Writes tests following strict TDD. Fails first, then implements. |
79
- | **security-auditor** | security | sonnet | Audits dependencies, scans for CVEs, reviews input validation. |
80
- | **docs-writer** | ai-governance | haiku | Writes clear, concise documentation. No fluff. |
81
- | **devops-engineer** | cicd | haiku | CI/CD pipelines, GitHub Actions, Docker, deployment automation. |
82
- | **release-engineer** | cicd | haiku | Version management, changelogs, multi-registry publishing. |
75
+ | Agent | Domain | Model | What it does |
76
+ | -------------------- | ------------- | ------ | -------------------------------------------------------------------------------- |
77
+ | **code-reviewer** | ai-governance | sonnet | Reviews changes for correctness, security, and conventions. Reports by severity. |
78
+ | **test-writer** | testing | sonnet | Writes tests following strict TDD. Fails first, then implements. |
79
+ | **security-auditor** | security | sonnet | Audits dependencies, scans for CVEs, reviews input validation. |
80
+ | **docs-writer** | ai-governance | haiku | Writes clear, concise documentation. No fluff. |
81
+ | **devops-engineer** | cicd | haiku | CI/CD pipelines, GitHub Actions, Docker, deployment automation. |
82
+ | **release-engineer** | cicd | haiku | Version management, changelogs, multi-registry publishing. |
83
83
 
84
84
  ### Opt-in Domains
85
85
 
@@ -98,13 +98,13 @@ builtins = ["rust", "python", "pyo3", "cicd", "docker", "default-commands"]
98
98
 
99
99
  ## Content Types
100
100
 
101
- | Type | Purpose | Example |
102
- |------|---------|---------|
103
- | **Rules** | What AI must/must not do | Security standards, coding conventions |
104
- | **Context** | What AI should know | Architecture docs, domain knowledge |
105
- | **Skills** | Reusable prompts and workflows | Deployment checklist, review protocol |
106
- | **Agents** | Specialized AI personas | Code reviewer, performance engineer |
107
- | **Commands** | Slash commands across tools | `/review`, `/deploy`, `/test` |
101
+ | Type | Purpose | Example |
102
+ | ------------ | ------------------------------ | -------------------------------------- |
103
+ | **Rules** | What AI must/must not do | Security standards, coding conventions |
104
+ | **Context** | What AI should know | Architecture docs, domain knowledge |
105
+ | **Skills** | Reusable prompts and workflows | Deployment checklist, review protocol |
106
+ | **Agents** | Specialized AI personas | Code reviewer, performance engineer |
107
+ | **Commands** | Slash commands across tools | `/review`, `/deploy`, `/test` |
108
108
 
109
109
  ## Organization at Scale
110
110
 
@@ -274,10 +274,10 @@ Add to `.pre-commit-config.yaml`:
274
274
  ```yaml
275
275
  repos:
276
276
  - repo: https://github.com/Goldziher/ai-rulez
277
- rev: v4.9.2
277
+ rev: v4.9.4
278
278
  hooks:
279
- - id: ai-rulez-recursive # generate outputs across the repo
280
- - id: ai-rulez-validate # dry-run validation
279
+ - id: ai-rulez-recursive # generate outputs across the repo
280
+ - id: ai-rulez-validate # dry-run validation
281
281
  ```
282
282
 
283
283
  Available hook ids: `ai-rulez-validate`, `ai-rulez-generate`,
@@ -296,7 +296,7 @@ repository needs. This requires AI-Rulez 4.9.0+ and Poly 0.14.0+:
296
296
  [[hooks.sources]]
297
297
  id = "ai-rulez"
298
298
  git = "https://github.com/Goldziher/ai-rulez.git"
299
- revision = "v4.9.0"
299
+ revision = "v4.9.3"
300
300
  hooks = ["ai-rulez-recursive", "ai-rulez-plugin-verify"]
301
301
  ```
302
302
 
package/ai-rulez.js CHANGED
@@ -9,31 +9,31 @@ const binaryName = getBinaryName(getPlatform().os);
9
9
  const binaryPath = path.join(__dirname, "bin", binaryName);
10
10
 
11
11
  function runBinary(args) {
12
- const child = spawn(binaryPath, args, {
13
- stdio: "inherit",
14
- });
12
+ const child = spawn(binaryPath, args, {
13
+ stdio: "inherit",
14
+ });
15
15
 
16
- child.on("close", (code) => {
17
- process.exit(code);
18
- });
16
+ child.on("close", (code) => {
17
+ process.exit(code);
18
+ });
19
19
  }
20
20
 
21
21
  async function main() {
22
- if (fs.existsSync(binaryPath)) {
23
- runBinary(process.argv.slice(2));
24
- } else {
25
- console.log(
26
- "🚀 First run detected - downloading ai-rulez binary...\n This will only happen once and takes a few seconds.\n",
27
- );
28
- const { install } = require("./install");
29
- try {
30
- await install(true); // Pass a flag to indicate this is a post-install step
31
- runBinary(process.argv.slice(2));
32
- } catch (error) {
33
- console.error("Failed to install or run ai-rulez:", error);
34
- process.exit(1);
35
- }
36
- }
22
+ if (fs.existsSync(binaryPath)) {
23
+ runBinary(process.argv.slice(2));
24
+ } else {
25
+ console.log(
26
+ "🚀 First run detected - downloading ai-rulez binary...\n This will only happen once and takes a few seconds.\n",
27
+ );
28
+ const { install } = require("./install");
29
+ try {
30
+ await install(true); // Pass a flag to indicate this is a post-install step
31
+ runBinary(process.argv.slice(2));
32
+ } catch (error) {
33
+ console.error("Failed to install or run ai-rulez:", error);
34
+ process.exit(1);
35
+ }
36
+ }
37
37
  }
38
38
 
39
- main();
39
+ main();
package/install.js CHANGED
@@ -14,481 +14,448 @@ const MAX_RETRIES = 3;
14
14
  const RETRY_DELAY = 2000;
15
15
 
16
16
  async function calculateSHA256(filePath) {
17
- return new Promise((resolve, reject) => {
18
- const hash = crypto.createHash("sha256");
19
- const stream = fs.createReadStream(filePath);
20
-
21
- stream.on("data", (data) => hash.update(data));
22
- stream.on("end", () => resolve(hash.digest("hex")));
23
- stream.on("error", reject);
24
- });
17
+ return new Promise((resolve, reject) => {
18
+ const hash = crypto.createHash("sha256");
19
+ const stream = fs.createReadStream(filePath);
20
+
21
+ stream.on("data", (data) => hash.update(data));
22
+ stream.on("end", () => resolve(hash.digest("hex")));
23
+ stream.on("error", reject);
24
+ });
25
25
  }
26
26
 
27
27
  async function getExpectedChecksum(checksumPath, filename) {
28
- try {
29
- const checksumContent = fs.readFileSync(checksumPath, "utf8");
30
- const lines = checksumContent.split("\n");
31
-
32
- for (const line of lines) {
33
- const parts = line.trim().split(/\s+/);
34
- if (parts.length >= 2 && parts[1] === filename) {
35
- return parts[0];
36
- }
37
- }
38
- return null;
39
- } catch (error) {
40
- console.warn("Warning: Could not parse checksums file:", error.message);
41
- return null;
42
- }
28
+ try {
29
+ const checksumContent = fs.readFileSync(checksumPath, "utf8");
30
+ const lines = checksumContent.split("\n");
31
+
32
+ for (const line of lines) {
33
+ const parts = line.trim().split(/\s+/);
34
+ if (parts.length >= 2 && parts[1] === filename) {
35
+ return parts[0];
36
+ }
37
+ }
38
+ return null;
39
+ } catch (error) {
40
+ console.warn("Warning: Could not parse checksums file:", error.message);
41
+ return null;
42
+ }
43
43
  }
44
44
 
45
45
  function getPlatform() {
46
- const platform = process.platform;
47
- const arch = process.arch;
48
-
49
- const platformMap = {
50
- darwin: "darwin",
51
- linux: "linux",
52
- win32: "windows",
53
- };
54
-
55
- const archMap = {
56
- x64: "amd64",
57
- arm64: "arm64",
58
- ia32: "386",
59
- x32: "386",
60
- };
61
-
62
- const mappedPlatform = platformMap[platform];
63
- const mappedArch = archMap[arch];
64
-
65
- if (!mappedPlatform) {
66
- throw new Error(
67
- `Unsupported operating system: ${platform}. Supported platforms: darwin (macOS), linux, win32 (Windows)`,
68
- );
69
- }
70
-
71
- if (!mappedArch) {
72
- throw new Error(
73
- `Unsupported architecture: ${arch}. Supported architectures: x64, arm64, ia32`,
74
- );
75
- }
76
-
77
- if (mappedPlatform === "windows" && mappedArch === "arm64") {
78
- throw new Error(
79
- "Windows ARM64 is not currently supported. Please use x64 or ia32 version.",
80
- );
81
- }
82
-
83
- return {
84
- os: mappedPlatform,
85
- arch: mappedArch,
86
- };
46
+ const platform = process.platform;
47
+ const arch = process.arch;
48
+
49
+ const platformMap = {
50
+ darwin: "darwin",
51
+ linux: "linux",
52
+ win32: "windows",
53
+ };
54
+
55
+ const archMap = {
56
+ x64: "amd64",
57
+ arm64: "arm64",
58
+ ia32: "386",
59
+ x32: "386",
60
+ };
61
+
62
+ const mappedPlatform = platformMap[platform];
63
+ const mappedArch = archMap[arch];
64
+
65
+ if (!mappedPlatform) {
66
+ throw new Error(
67
+ `Unsupported operating system: ${platform}. Supported platforms: darwin (macOS), linux, win32 (Windows)`,
68
+ );
69
+ }
70
+
71
+ if (!mappedArch) {
72
+ throw new Error(`Unsupported architecture: ${arch}. Supported architectures: x64, arm64, ia32`);
73
+ }
74
+
75
+ if (mappedPlatform === "windows" && mappedArch === "arm64") {
76
+ throw new Error("Windows ARM64 is not currently supported. Please use x64 or ia32 version.");
77
+ }
78
+
79
+ return {
80
+ os: mappedPlatform,
81
+ arch: mappedArch,
82
+ };
87
83
  }
88
84
 
89
85
  function getBinaryName(platform) {
90
- return platform === "windows" ? "ai-rulez-bin.exe" : "ai-rulez-bin";
86
+ return platform === "windows" ? "ai-rulez-bin.exe" : "ai-rulez-bin";
91
87
  }
92
88
 
93
89
  function getPackagedBinaryCandidates(os, arch) {
94
- const baseName = `ai-rulez-${os}-${arch}`;
95
- if (os === "windows") {
96
- return [`${baseName}.exe`, baseName];
97
- }
98
- return [baseName];
90
+ const baseName = `ai-rulez-${os}-${arch}`;
91
+ if (os === "windows") {
92
+ return [`${baseName}.exe`, baseName];
93
+ }
94
+ return [baseName];
99
95
  }
100
96
 
101
97
  function usePackagedBinaryIfAvailable(os, arch, binaryName, binDir) {
102
- const packagedBinaryCandidates = getPackagedBinaryCandidates(os, arch);
103
- const binaryPath = path.join(binDir, binaryName);
98
+ const packagedBinaryCandidates = getPackagedBinaryCandidates(os, arch);
99
+ const binaryPath = path.join(binDir, binaryName);
104
100
 
105
- for (const candidateName of packagedBinaryCandidates) {
106
- const packagedBinaryPath = path.join(binDir, candidateName);
107
- if (!fs.existsSync(packagedBinaryPath)) {
108
- continue;
109
- }
101
+ for (const candidateName of packagedBinaryCandidates) {
102
+ const packagedBinaryPath = path.join(binDir, candidateName);
103
+ if (!fs.existsSync(packagedBinaryPath)) {
104
+ continue;
105
+ }
110
106
 
111
- if (path.resolve(packagedBinaryPath) !== path.resolve(binaryPath)) {
112
- fs.copyFileSync(packagedBinaryPath, binaryPath);
113
- }
107
+ if (path.resolve(packagedBinaryPath) !== path.resolve(binaryPath)) {
108
+ fs.copyFileSync(packagedBinaryPath, binaryPath);
109
+ }
114
110
 
115
- if (os !== "windows") {
116
- fs.chmodSync(binaryPath, 0o755);
117
- }
111
+ if (os !== "windows") {
112
+ fs.chmodSync(binaryPath, 0o755);
113
+ }
118
114
 
119
- console.log(`Using packaged binary for ${os}/${arch} (${candidateName})`);
120
- return true;
121
- }
115
+ console.log(`Using packaged binary for ${os}/${arch} (${candidateName})`);
116
+ return true;
117
+ }
122
118
 
123
- return false;
119
+ return false;
124
120
  }
125
121
 
126
122
  async function downloadBinary(url, dest, retryCount = 0) {
127
- return new Promise((resolve, reject) => {
128
- const file = fs.createWriteStream(dest);
129
- const protocol = url.startsWith("https") ? https : http;
130
-
131
- const request = protocol.get(
132
- url,
133
- { timeout: DOWNLOAD_TIMEOUT },
134
- (response) => {
135
- if (response.statusCode === 302 || response.statusCode === 301) {
136
- file.close();
137
- try {
138
- fs.unlinkSync(dest);
139
- } catch {}
140
- downloadBinary(response.headers.location, dest, retryCount)
141
- .then(resolve)
142
- .catch(reject);
143
- return;
144
- }
145
-
146
- if (response.statusCode !== 200) {
147
- file.close();
148
- try {
149
- fs.unlinkSync(dest);
150
- } catch {}
151
- const error = new Error(
152
- `HTTP ${response.statusCode}: ${response.statusMessage}`,
153
- );
154
-
155
- if (retryCount < MAX_RETRIES) {
156
- console.log(
157
- `Download failed, retrying in ${RETRY_DELAY / 1000}s... (${retryCount + 1}/${MAX_RETRIES})`,
158
- );
159
- setTimeout(() => {
160
- downloadBinary(url, dest, retryCount + 1)
161
- .then(resolve)
162
- .catch(reject);
163
- }, RETRY_DELAY);
164
- return;
165
- }
166
-
167
- reject(error);
168
- return;
169
- }
170
-
171
- let downloadedBytes = 0;
172
- response.on("data", (chunk) => {
173
- downloadedBytes += chunk.length;
174
- });
175
-
176
- response.pipe(file);
177
-
178
- file.on("finish", () => {
179
- file.close();
180
- if (downloadedBytes === 0) {
181
- try {
182
- fs.unlinkSync(dest);
183
- } catch {}
184
- reject(new Error("Downloaded file is empty"));
185
- return;
186
- }
187
- console.log(`Downloaded ${downloadedBytes} bytes`);
188
- resolve();
189
- });
190
-
191
- file.on("error", (err) => {
192
- file.close();
193
- try {
194
- fs.unlinkSync(dest);
195
- } catch {}
196
- reject(err);
197
- });
198
- },
199
- );
200
-
201
- request.on("timeout", () => {
202
- request.destroy();
203
- file.close();
204
- try {
205
- fs.unlinkSync(dest);
206
- } catch {}
207
-
208
- if (retryCount < MAX_RETRIES) {
209
- console.log(
210
- `Download timeout, retrying in ${RETRY_DELAY / 1000}s... (${retryCount + 1}/${MAX_RETRIES})`,
211
- );
212
- setTimeout(() => {
213
- downloadBinary(url, dest, retryCount + 1)
214
- .then(resolve)
215
- .catch(reject);
216
- }, RETRY_DELAY);
217
- return;
218
- }
219
-
220
- reject(new Error("Download timeout after multiple retries"));
221
- });
222
-
223
- request.on("error", (err) => {
224
- file.close();
225
- try {
226
- fs.unlinkSync(dest);
227
- } catch {}
228
-
229
- if (retryCount < MAX_RETRIES) {
230
- console.log(
231
- `Download error, retrying in ${RETRY_DELAY / 1000}s... (${retryCount + 1}/${MAX_RETRIES})`,
232
- );
233
- setTimeout(() => {
234
- downloadBinary(url, dest, retryCount + 1)
235
- .then(resolve)
236
- .catch(reject);
237
- }, RETRY_DELAY);
238
- return;
239
- }
240
-
241
- reject(err);
242
- });
243
- });
123
+ return new Promise((resolve, reject) => {
124
+ const file = fs.createWriteStream(dest);
125
+ const protocol = url.startsWith("https") ? https : http;
126
+
127
+ const request = protocol.get(url, { timeout: DOWNLOAD_TIMEOUT }, (response) => {
128
+ if (response.statusCode === 302 || response.statusCode === 301) {
129
+ file.close();
130
+ try {
131
+ fs.unlinkSync(dest);
132
+ } catch {}
133
+ downloadBinary(response.headers.location, dest, retryCount).then(resolve).catch(reject);
134
+ return;
135
+ }
136
+
137
+ if (response.statusCode !== 200) {
138
+ file.close();
139
+ try {
140
+ fs.unlinkSync(dest);
141
+ } catch {}
142
+ const error = new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`);
143
+
144
+ if (retryCount < MAX_RETRIES) {
145
+ console.log(
146
+ `Download failed, retrying in ${RETRY_DELAY / 1000}s... (${retryCount + 1}/${MAX_RETRIES})`,
147
+ );
148
+ setTimeout(() => {
149
+ downloadBinary(url, dest, retryCount + 1)
150
+ .then(resolve)
151
+ .catch(reject);
152
+ }, RETRY_DELAY);
153
+ return;
154
+ }
155
+
156
+ reject(error);
157
+ return;
158
+ }
159
+
160
+ let downloadedBytes = 0;
161
+ response.on("data", (chunk) => {
162
+ downloadedBytes += chunk.length;
163
+ });
164
+
165
+ response.pipe(file);
166
+
167
+ file.on("finish", () => {
168
+ file.close();
169
+ if (downloadedBytes === 0) {
170
+ try {
171
+ fs.unlinkSync(dest);
172
+ } catch {}
173
+ reject(new Error("Downloaded file is empty"));
174
+ return;
175
+ }
176
+ console.log(`Downloaded ${downloadedBytes} bytes`);
177
+ resolve();
178
+ });
179
+
180
+ file.on("error", (err) => {
181
+ file.close();
182
+ try {
183
+ fs.unlinkSync(dest);
184
+ } catch {}
185
+ reject(err);
186
+ });
187
+ });
188
+
189
+ request.on("timeout", () => {
190
+ request.destroy();
191
+ file.close();
192
+ try {
193
+ fs.unlinkSync(dest);
194
+ } catch {}
195
+
196
+ if (retryCount < MAX_RETRIES) {
197
+ console.log(
198
+ `Download timeout, retrying in ${RETRY_DELAY / 1000}s... (${retryCount + 1}/${MAX_RETRIES})`,
199
+ );
200
+ setTimeout(() => {
201
+ downloadBinary(url, dest, retryCount + 1)
202
+ .then(resolve)
203
+ .catch(reject);
204
+ }, RETRY_DELAY);
205
+ return;
206
+ }
207
+
208
+ reject(new Error("Download timeout after multiple retries"));
209
+ });
210
+
211
+ request.on("error", (err) => {
212
+ file.close();
213
+ try {
214
+ fs.unlinkSync(dest);
215
+ } catch {}
216
+
217
+ if (retryCount < MAX_RETRIES) {
218
+ console.log(
219
+ `Download error, retrying in ${RETRY_DELAY / 1000}s... (${retryCount + 1}/${MAX_RETRIES})`,
220
+ );
221
+ setTimeout(() => {
222
+ downloadBinary(url, dest, retryCount + 1)
223
+ .then(resolve)
224
+ .catch(reject);
225
+ }, RETRY_DELAY);
226
+ return;
227
+ }
228
+
229
+ reject(err);
230
+ });
231
+ });
244
232
  }
245
233
 
246
234
  async function extractArchive(archivePath, extractDir, platform) {
247
- if (platform === "windows") {
248
- const escapedArchivePath = archivePath.replace(/'/g, "''");
249
- const escapedExtractDir = extractDir.replace(/'/g, "''");
250
-
251
- const powershellCommand = [
252
- "powershell.exe",
253
- "-NoProfile",
254
- "-ExecutionPolicy",
255
- "Bypass",
256
- "-Command",
257
- `Expand-Archive -LiteralPath '${escapedArchivePath}' -DestinationPath '${escapedExtractDir}' -Force`,
258
- ];
259
-
260
- await new Promise((resolve, reject) => {
261
- const child = spawn(powershellCommand[0], powershellCommand.slice(1), {
262
- stdio: ["pipe", "pipe", "pipe"],
263
- windowsHide: true,
264
- });
265
-
266
- let stderr = "";
267
- child.stderr.on("data", (data) => {
268
- stderr += data.toString();
269
- });
270
-
271
- child.on("close", (code) => {
272
- if (code === 0) {
273
- resolve();
274
- } else {
275
- reject(
276
- new Error(
277
- `PowerShell extraction failed with code ${code}: ${stderr}`,
278
- ),
279
- );
280
- }
281
- });
282
-
283
- child.on("error", reject);
284
- });
285
- } else {
286
- await new Promise((resolve, reject) => {
287
- const child = spawn("tar", ["-xzf", archivePath, "-C", extractDir], {
288
- stdio: ["pipe", "pipe", "pipe"],
289
- });
290
-
291
- let stderr = "";
292
- child.stderr.on("data", (data) => {
293
- stderr += data.toString();
294
- });
295
-
296
- child.on("close", (code) => {
297
- if (code === 0) {
298
- resolve();
299
- } else {
300
- reject(
301
- new Error(`tar extraction failed with code ${code}: ${stderr}`),
302
- );
303
- }
304
- });
305
-
306
- child.on("error", reject);
307
- });
308
- }
235
+ if (platform === "windows") {
236
+ const escapedArchivePath = archivePath.replace(/'/g, "''");
237
+ const escapedExtractDir = extractDir.replace(/'/g, "''");
238
+
239
+ const powershellCommand = [
240
+ "powershell.exe",
241
+ "-NoProfile",
242
+ "-ExecutionPolicy",
243
+ "Bypass",
244
+ "-Command",
245
+ `Expand-Archive -LiteralPath '${escapedArchivePath}' -DestinationPath '${escapedExtractDir}' -Force`,
246
+ ];
247
+
248
+ await new Promise((resolve, reject) => {
249
+ const child = spawn(powershellCommand[0], powershellCommand.slice(1), {
250
+ stdio: ["pipe", "pipe", "pipe"],
251
+ windowsHide: true,
252
+ });
253
+
254
+ let stderr = "";
255
+ child.stderr.on("data", (data) => {
256
+ stderr += data.toString();
257
+ });
258
+
259
+ child.on("close", (code) => {
260
+ if (code === 0) {
261
+ resolve();
262
+ } else {
263
+ reject(new Error(`PowerShell extraction failed with code ${code}: ${stderr}`));
264
+ }
265
+ });
266
+
267
+ child.on("error", reject);
268
+ });
269
+ } else {
270
+ await new Promise((resolve, reject) => {
271
+ const child = spawn("tar", ["-xzf", archivePath, "-C", extractDir], {
272
+ stdio: ["pipe", "pipe", "pipe"],
273
+ });
274
+
275
+ let stderr = "";
276
+ child.stderr.on("data", (data) => {
277
+ stderr += data.toString();
278
+ });
279
+
280
+ child.on("close", (code) => {
281
+ if (code === 0) {
282
+ resolve();
283
+ } else {
284
+ reject(new Error(`tar extraction failed with code ${code}: ${stderr}`));
285
+ }
286
+ });
287
+
288
+ child.on("error", reject);
289
+ });
290
+ }
309
291
  }
310
292
 
311
293
  async function install(isPostInstall = false) {
312
- const DEBUG = process.env.AI_RULEZ_DEBUG === "1";
313
-
314
- try {
315
- if (DEBUG) console.error("[install.js] Starting installation");
316
-
317
- const nodeVersion = process.version;
318
- const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0], 10);
319
- if (majorVersion < 20) {
320
- console.error(
321
- `Error: Node.js ${nodeVersion} is not supported. Please upgrade to Node.js 20 or later.`,
322
- );
323
- process.exit(1);
324
- }
325
-
326
- const { os, arch } = getPlatform();
327
- const binaryName = getBinaryName(os);
328
- const binDir = path.join(__dirname, "bin");
329
-
330
- if (DEBUG)
331
- console.error(
332
- `[install.js] Platform: ${os}/${arch}, Binary name: ${binaryName}`,
333
- );
334
- if (!fs.existsSync(binDir)) {
335
- fs.mkdirSync(binDir, { recursive: true });
336
- }
337
-
338
- if (usePackagedBinaryIfAvailable(os, arch, binaryName, binDir)) {
339
- if (DEBUG) {
340
- console.error("[install.js] Packaged binary detected, skipping download");
341
- }
342
- if (!isPostInstall) {
343
- process.exit(0);
344
- }
345
- return;
346
- }
347
- const packageJson = JSON.parse(
348
- fs.readFileSync(path.join(__dirname, "package.json"), "utf8"),
349
- );
350
- const version = packageJson.version;
351
-
352
- const archiveExt = os === "windows" ? "zip" : "tar.gz";
353
- const archiveName = `ai-rulez_${version}_${os}_${arch}.${archiveExt}`;
354
- const downloadUrl = `https://github.com/${REPO_NAME}/releases/download/v${version}/${archiveName}`;
355
- const checksumUrl = `https://github.com/${REPO_NAME}/releases/download/v${version}/checksums.txt`;
356
-
357
- console.log(`Downloading ai-rulez ${version} for ${os}/${arch}...`);
358
- console.log(`URL: ${downloadUrl}`);
359
- const archivePath = path.join(__dirname, archiveName);
360
-
361
- console.log("Downloading checksums...");
362
- const checksumPath = path.join(__dirname, "checksums.txt");
363
- try {
364
- await downloadBinary(checksumUrl, checksumPath);
365
- } catch (_checksumError) {
366
- console.warn(
367
- "Warning: Could not download checksums, skipping verification",
368
- );
369
- }
370
-
371
- await downloadBinary(downloadUrl, archivePath);
372
-
373
- if (fs.existsSync(checksumPath)) {
374
- console.log("Verifying checksum...");
375
- const expectedHash = await getExpectedChecksum(checksumPath, archiveName);
376
- if (expectedHash) {
377
- const actualHash = await calculateSHA256(archivePath);
378
- if (actualHash !== expectedHash) {
379
- throw new Error(
380
- `Checksum verification failed. Expected: ${expectedHash}, Got: ${actualHash}`,
381
- );
382
- }
383
- console.log("✓ Checksum verified");
384
- }
385
- fs.unlinkSync(checksumPath);
386
- }
387
-
388
- console.log("Extracting binary...");
389
-
390
- const tempExtractDir = path.join(__dirname, ".extract-temp");
391
- if (fs.existsSync(tempExtractDir)) {
392
- fs.rmSync(tempExtractDir, { recursive: true, force: true });
393
- }
394
- fs.mkdirSync(tempExtractDir, { recursive: true });
395
-
396
- await extractArchive(archivePath, tempExtractDir, os);
397
-
398
- const extractedName = os === "windows" ? "ai-rulez.exe" : "ai-rulez";
399
- const extractedPath = path.join(tempExtractDir, extractedName);
400
- const binaryPath = path.join(binDir, binaryName);
401
-
402
- if (fs.existsSync(extractedPath)) {
403
- if (fs.existsSync(binaryPath)) {
404
- fs.unlinkSync(binaryPath);
405
- }
406
- fs.renameSync(extractedPath, binaryPath);
407
- }
408
-
409
- fs.rmSync(tempExtractDir, { recursive: true, force: true });
410
-
411
- if (!fs.existsSync(binaryPath)) {
412
- throw new Error(`Binary not found after extraction: ${binaryPath}`);
413
- }
414
-
415
- if (os !== "windows") {
416
- fs.chmodSync(binaryPath, 0o755);
417
- }
418
-
419
- try {
420
- await new Promise((resolve, reject) => {
421
- const testCommand =
422
- os === "windows"
423
- ? [binaryPath, "--version"]
424
- : [binaryPath, "--version"];
425
- const child = spawn(testCommand[0], testCommand.slice(1), {
426
- stdio: ["pipe", "pipe", "pipe"],
427
- timeout: 5000,
428
- });
429
-
430
- child.on("close", (_code) => {
431
- resolve();
432
- });
433
-
434
- child.on("error", (err) => {
435
- if (err.code === "ENOENT") {
436
- reject(new Error("Downloaded binary is not executable"));
437
- } else {
438
- resolve();
439
- }
440
- });
441
- });
442
- } catch (verifyError) {
443
- console.warn(
444
- "Warning: Could not verify binary execution:",
445
- verifyError.message,
446
- );
447
- }
448
-
449
- fs.unlinkSync(archivePath);
450
-
451
- console.log(
452
- `✅ ai-rulez ${version} installed successfully for ${os}/${arch}!`,
453
- );
454
-
455
- if (DEBUG) {
456
- console.error(`[install.js] Installation complete`);
457
- console.error(`[install.js] Binary location: ${binaryPath}`);
458
- console.error(`[install.js] Exiting with code 0`);
459
- }
460
-
461
- if (!isPostInstall) {
462
- process.exit(0);
463
- }
464
- } catch (error) {
465
- if (DEBUG)
466
- console.error(`[install.js] Installation failed: ${error.message}`);
467
- console.error("Failed to install ai-rulez binary:", error.message);
468
- console.error("You can manually download the binary from:");
469
- console.error(`https://github.com/${REPO_NAME}/releases`);
470
- if (!isPostInstall) {
471
- process.exit(1);
472
- } else {
473
- throw error;
474
- }
475
- }
294
+ const DEBUG = process.env.AI_RULEZ_DEBUG === "1";
295
+
296
+ try {
297
+ if (DEBUG) console.error("[install.js] Starting installation");
298
+
299
+ const nodeVersion = process.version;
300
+ const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0], 10);
301
+ if (majorVersion < 20) {
302
+ console.error(
303
+ `Error: Node.js ${nodeVersion} is not supported. Please upgrade to Node.js 20 or later.`,
304
+ );
305
+ process.exit(1);
306
+ }
307
+
308
+ const { os, arch } = getPlatform();
309
+ const binaryName = getBinaryName(os);
310
+ const binDir = path.join(__dirname, "bin");
311
+
312
+ if (DEBUG) console.error(`[install.js] Platform: ${os}/${arch}, Binary name: ${binaryName}`);
313
+ if (!fs.existsSync(binDir)) {
314
+ fs.mkdirSync(binDir, { recursive: true });
315
+ }
316
+
317
+ if (usePackagedBinaryIfAvailable(os, arch, binaryName, binDir)) {
318
+ if (DEBUG) {
319
+ console.error("[install.js] Packaged binary detected, skipping download");
320
+ }
321
+ if (!isPostInstall) {
322
+ process.exit(0);
323
+ }
324
+ return;
325
+ }
326
+ const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"));
327
+ const version = packageJson.version;
328
+
329
+ const archiveExt = os === "windows" ? "zip" : "tar.gz";
330
+ const archiveName = `ai-rulez_${version}_${os}_${arch}.${archiveExt}`;
331
+ const downloadUrl = `https://github.com/${REPO_NAME}/releases/download/v${version}/${archiveName}`;
332
+ const checksumUrl = `https://github.com/${REPO_NAME}/releases/download/v${version}/checksums.txt`;
333
+
334
+ console.log(`Downloading ai-rulez ${version} for ${os}/${arch}...`);
335
+ console.log(`URL: ${downloadUrl}`);
336
+ const archivePath = path.join(__dirname, archiveName);
337
+
338
+ console.log("Downloading checksums...");
339
+ const checksumPath = path.join(__dirname, "checksums.txt");
340
+ try {
341
+ await downloadBinary(checksumUrl, checksumPath);
342
+ } catch (_checksumError) {
343
+ console.warn("Warning: Could not download checksums, skipping verification");
344
+ }
345
+
346
+ await downloadBinary(downloadUrl, archivePath);
347
+
348
+ if (fs.existsSync(checksumPath)) {
349
+ console.log("Verifying checksum...");
350
+ const expectedHash = await getExpectedChecksum(checksumPath, archiveName);
351
+ if (expectedHash) {
352
+ const actualHash = await calculateSHA256(archivePath);
353
+ if (actualHash !== expectedHash) {
354
+ throw new Error(
355
+ `Checksum verification failed. Expected: ${expectedHash}, Got: ${actualHash}`,
356
+ );
357
+ }
358
+ console.log("✓ Checksum verified");
359
+ }
360
+ fs.unlinkSync(checksumPath);
361
+ }
362
+
363
+ console.log("Extracting binary...");
364
+
365
+ const tempExtractDir = path.join(__dirname, ".extract-temp");
366
+ if (fs.existsSync(tempExtractDir)) {
367
+ fs.rmSync(tempExtractDir, { recursive: true, force: true });
368
+ }
369
+ fs.mkdirSync(tempExtractDir, { recursive: true });
370
+
371
+ await extractArchive(archivePath, tempExtractDir, os);
372
+
373
+ const extractedName = os === "windows" ? "ai-rulez.exe" : "ai-rulez";
374
+ const extractedPath = path.join(tempExtractDir, extractedName);
375
+ const binaryPath = path.join(binDir, binaryName);
376
+
377
+ if (fs.existsSync(extractedPath)) {
378
+ if (fs.existsSync(binaryPath)) {
379
+ fs.unlinkSync(binaryPath);
380
+ }
381
+ fs.renameSync(extractedPath, binaryPath);
382
+ }
383
+
384
+ fs.rmSync(tempExtractDir, { recursive: true, force: true });
385
+
386
+ if (!fs.existsSync(binaryPath)) {
387
+ throw new Error(`Binary not found after extraction: ${binaryPath}`);
388
+ }
389
+
390
+ if (os !== "windows") {
391
+ fs.chmodSync(binaryPath, 0o755);
392
+ }
393
+
394
+ try {
395
+ await new Promise((resolve, reject) => {
396
+ const testCommand =
397
+ os === "windows" ? [binaryPath, "--version"] : [binaryPath, "--version"];
398
+ const child = spawn(testCommand[0], testCommand.slice(1), {
399
+ stdio: ["pipe", "pipe", "pipe"],
400
+ timeout: 5000,
401
+ });
402
+
403
+ child.on("close", (_code) => {
404
+ resolve();
405
+ });
406
+
407
+ child.on("error", (err) => {
408
+ if (err.code === "ENOENT") {
409
+ reject(new Error("Downloaded binary is not executable"));
410
+ } else {
411
+ resolve();
412
+ }
413
+ });
414
+ });
415
+ } catch (verifyError) {
416
+ console.warn("Warning: Could not verify binary execution:", verifyError.message);
417
+ }
418
+
419
+ fs.unlinkSync(archivePath);
420
+
421
+ console.log(`✅ ai-rulez ${version} installed successfully for ${os}/${arch}!`);
422
+
423
+ if (DEBUG) {
424
+ console.error(`[install.js] Installation complete`);
425
+ console.error(`[install.js] Binary location: ${binaryPath}`);
426
+ console.error(`[install.js] Exiting with code 0`);
427
+ }
428
+
429
+ if (!isPostInstall) {
430
+ process.exit(0);
431
+ }
432
+ } catch (error) {
433
+ if (DEBUG) console.error(`[install.js] Installation failed: ${error.message}`);
434
+ console.error("Failed to install ai-rulez binary:", error.message);
435
+ console.error("You can manually download the binary from:");
436
+ console.error(`https://github.com/${REPO_NAME}/releases`);
437
+ if (!isPostInstall) {
438
+ process.exit(1);
439
+ } else {
440
+ throw error;
441
+ }
442
+ }
476
443
  }
477
444
 
478
445
  if (typeof module !== "undefined" && module.exports) {
479
- module.exports = {
480
- getPlatform,
481
- getBinaryName,
482
- getPackagedBinaryCandidates,
483
- usePackagedBinaryIfAvailable,
484
- downloadBinary,
485
- extractArchive,
486
- calculateSHA256,
487
- getExpectedChecksum,
488
- install,
489
- };
446
+ module.exports = {
447
+ getPlatform,
448
+ getBinaryName,
449
+ getPackagedBinaryCandidates,
450
+ usePackagedBinaryIfAvailable,
451
+ downloadBinary,
452
+ extractArchive,
453
+ calculateSHA256,
454
+ getExpectedChecksum,
455
+ install,
456
+ };
490
457
  }
491
458
 
492
459
  if (require.main === module) {
493
- install(false);
460
+ install(false);
494
461
  }
package/package.json CHANGED
@@ -1,53 +1,45 @@
1
1
  {
2
2
  "name": "ai-rulez",
3
- "version": "4.9.2",
3
+ "version": "4.9.4",
4
4
  "description": "Complete AI development workflow for 19+ tools. Ships with builtin rules, agents, and conventions. Generate native configs for Claude, Cursor, Copilot, Windsurf, Gemini, Codex and more from a single source.",
5
5
  "keywords": [
6
+ "agents",
6
7
  "ai",
7
8
  "ai-assistant",
9
+ "ai-development",
8
10
  "ai-rules",
11
+ "assistant-configuration",
12
+ "automation",
9
13
  "claude",
10
- "cursor",
11
- "copilot",
12
- "windsurf",
13
- "gemini",
14
- "cline",
15
- "continue-dev",
16
- "mcp",
17
- "model-context-protocol",
18
14
  "cli",
19
- "configuration",
15
+ "cline",
16
+ "code-generation",
20
17
  "config",
21
- "rules",
22
- "generator",
23
- "golang",
24
- "go",
25
- "development",
18
+ "configuration",
19
+ "continue-dev",
20
+ "copilot",
21
+ "cursor",
26
22
  "developer-tools",
27
- "automation",
28
- "workflow",
29
- "productivity",
30
- "pre-commit",
23
+ "development",
24
+ "gemini",
25
+ "generator",
31
26
  "git-hooks",
32
- "code-generation",
33
- "ai-development",
34
- "assistant-configuration",
27
+ "go",
28
+ "golang",
29
+ "mcp",
30
+ "model-context-protocol",
35
31
  "monorepo",
32
+ "pre-commit",
36
33
  "presets",
37
- "agents"
34
+ "productivity",
35
+ "rules",
36
+ "windsurf",
37
+ "workflow"
38
38
  ],
39
- "repository": {
40
- "type": "git",
41
- "url": "https://github.com/Goldziher/ai-rulez.git"
42
- },
43
39
  "homepage": "https://goldziher.github.io/ai-rulez/",
44
40
  "bugs": {
45
41
  "url": "https://github.com/Goldziher/ai-rulez/issues"
46
42
  },
47
- "funding": {
48
- "type": "github",
49
- "url": "https://github.com/sponsors/Goldziher"
50
- },
51
43
  "license": "MIT",
52
44
  "author": {
53
45
  "name": "Na'aman Hirschfeld",
@@ -61,19 +53,23 @@
61
53
  "url": "https://github.com/Goldziher"
62
54
  }
63
55
  ],
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "https://github.com/Goldziher/ai-rulez.git"
59
+ },
60
+ "funding": {
61
+ "type": "github",
62
+ "url": "https://github.com/sponsors/Goldziher"
63
+ },
64
64
  "bin": {
65
65
  "ai-rulez": "./ai-rulez.js"
66
66
  },
67
- "scripts": {},
68
67
  "files": [
69
68
  "ai-rulez.js",
70
69
  "install.js",
71
70
  "README.md",
72
71
  "bin/ai-rulez-*"
73
72
  ],
74
- "engines": {
75
- "node": ">=14.0.0"
76
- },
77
73
  "os": [
78
74
  "darwin",
79
75
  "linux",
@@ -83,5 +79,9 @@
83
79
  "x64",
84
80
  "arm64",
85
81
  "ia32"
86
- ]
82
+ ],
83
+ "scripts": {},
84
+ "engines": {
85
+ "node": ">=14.0.0"
86
+ }
87
87
  }