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.
- package/README.md +29 -29
- package/ai-rulez.js +22 -22
- package/install.js +409 -442
- 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
|
|
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
|
|
62
|
-
|
|
63
|
-
| **ai-governance**
|
|
64
|
-
| **code-quality**
|
|
65
|
-
| **testing**
|
|
66
|
-
| **git-workflow**
|
|
67
|
-
| **security**
|
|
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
|
|
76
|
-
|
|
77
|
-
| **code-reviewer**
|
|
78
|
-
| **test-writer**
|
|
79
|
-
| **security-auditor** | security
|
|
80
|
-
| **docs-writer**
|
|
81
|
-
| **devops-engineer**
|
|
82
|
-
| **release-engineer** | cicd
|
|
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
|
|
102
|
-
|
|
103
|
-
| **Rules**
|
|
104
|
-
| **Context**
|
|
105
|
-
| **Skills**
|
|
106
|
-
| **Agents**
|
|
107
|
-
| **Commands** | Slash commands across tools
|
|
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.
|
|
277
|
+
rev: v4.9.4
|
|
278
278
|
hooks:
|
|
279
|
-
- id: ai-rulez-recursive
|
|
280
|
-
- id: ai-rulez-validate
|
|
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.
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const child = spawn(binaryPath, args, {
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
});
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
child.on("close", (code) => {
|
|
17
|
+
process.exit(code);
|
|
18
|
+
});
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async function main() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
86
|
+
return platform === "windows" ? "ai-rulez-bin.exe" : "ai-rulez-bin";
|
|
91
87
|
}
|
|
92
88
|
|
|
93
89
|
function getPackagedBinaryCandidates(os, arch) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
98
|
+
const packagedBinaryCandidates = getPackagedBinaryCandidates(os, arch);
|
|
99
|
+
const binaryPath = path.join(binDir, binaryName);
|
|
104
100
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
if (path.resolve(packagedBinaryPath) !== path.resolve(binaryPath)) {
|
|
108
|
+
fs.copyFileSync(packagedBinaryPath, binaryPath);
|
|
109
|
+
}
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
if (os !== "windows") {
|
|
112
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
113
|
+
}
|
|
118
114
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
console.log(`Using packaged binary for ${os}/${arch} (${candidateName})`);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
122
118
|
|
|
123
|
-
|
|
119
|
+
return false;
|
|
124
120
|
}
|
|
125
121
|
|
|
126
122
|
async function downloadBinary(url, dest, retryCount = 0) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
460
|
+
install(false);
|
|
494
461
|
}
|
package/package.json
CHANGED
|
@@ -1,53 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-rulez",
|
|
3
|
-
"version": "4.9.
|
|
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
|
-
"
|
|
15
|
+
"cline",
|
|
16
|
+
"code-generation",
|
|
20
17
|
"config",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"development",
|
|
18
|
+
"configuration",
|
|
19
|
+
"continue-dev",
|
|
20
|
+
"copilot",
|
|
21
|
+
"cursor",
|
|
26
22
|
"developer-tools",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"pre-commit",
|
|
23
|
+
"development",
|
|
24
|
+
"gemini",
|
|
25
|
+
"generator",
|
|
31
26
|
"git-hooks",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
27
|
+
"go",
|
|
28
|
+
"golang",
|
|
29
|
+
"mcp",
|
|
30
|
+
"model-context-protocol",
|
|
35
31
|
"monorepo",
|
|
32
|
+
"pre-commit",
|
|
36
33
|
"presets",
|
|
37
|
-
"
|
|
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
|
}
|