@lovrozagar/grunt 0.3.10 → 0.4.0
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/.agents/agents/grunt.md +1 -0
- package/.agents/agents/implementer.md +3 -2
- package/.agents/agents/orchestrator.md +4 -3
- package/.agents/skills/browser/SKILL.md +17 -0
- package/.agents/skills/handoff/SKILL.md +2 -2
- package/.agents/skills/pickup/SKILL.md +64 -0
- package/.agents/skills/solo/SKILL.md +1 -1
- package/.claude/agents/grunt.md +1 -0
- package/.claude/agents/implementer.md +3 -2
- package/.claude/agents/orchestrator.md +4 -3
- package/.claude/skills/browser/SKILL.md +17 -0
- package/.claude/skills/handoff/SKILL.md +2 -2
- package/.claude/skills/pickup/SKILL.md +64 -0
- package/.claude/skills/solo/SKILL.md +1 -1
- package/.codex/agents/grunt.toml +1 -0
- package/.codex/agents/implementer.toml +3 -2
- package/.codex/agents/orchestrator.toml +4 -3
- package/.grok/agents/grunt.md +1 -0
- package/.grok/agents/implementer.md +3 -2
- package/.grok/agents/orchestrator.md +4 -3
- package/.grok/hooks/orchestrate-parent.js +4 -3
- package/.grok/skills/browser/SKILL.md +17 -0
- package/.grok/skills/handoff/SKILL.md +2 -2
- package/.grok/skills/pickup/SKILL.md +64 -0
- package/.grok/skills/solo/SKILL.md +1 -1
- package/.rulesync/reference/browser.md +59 -0
- package/.rulesync/reference/cascade.md +2 -1
- package/.rulesync/reference/hooks.md +1 -1
- package/.rulesync/reference/map.md +6 -3
- package/.rulesync/reference/rtk.md +4 -1
- package/.rulesync/rules/CLAUDE.md +3 -2
- package/.rulesync/rules/overview.md +3 -2
- package/.rulesync/skills/browser/SKILL.md +19 -0
- package/.rulesync/skills/handoff/SKILL.md +2 -2
- package/.rulesync/skills/pickup/SKILL.md +64 -0
- package/.rulesync/skills/solo/SKILL.md +1 -1
- package/.rulesync/subagents/grunt.md +1 -0
- package/.rulesync/subagents/implementer.md +3 -2
- package/.rulesync/subagents/orchestrator.md +4 -3
- package/AGENTS.md +3 -2
- package/CLAUDE.md +3 -2
- package/README.md +35 -5
- package/cli/grunt.mjs +2 -2
- package/cli/grunt.test.ts +3 -3
- package/cli/init.mjs +2 -0
- package/cli/init.test.ts +2 -0
- package/package.json +5 -2
- package/scripts/browser.mjs +764 -0
- package/scripts/doctor.mjs +252 -0
- package/scripts/gate-fat-tools.mjs +19 -4
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Unified prereq doctor. Print-only install hints. Never runs installs. */
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
export const CHROMIUM_BINS = [
|
|
9
|
+
"chromium",
|
|
10
|
+
"chromium-browser",
|
|
11
|
+
"google-chrome",
|
|
12
|
+
"google-chrome-stable",
|
|
13
|
+
"chrome",
|
|
14
|
+
"msedge",
|
|
15
|
+
"microsoft-edge",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export const LIGHTPANDA_REPO = "https://github.com/lightpanda-io/browser";
|
|
19
|
+
const NODE_URL = "https://nodejs.org";
|
|
20
|
+
const RTK_CURL =
|
|
21
|
+
"curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/master/install.sh | sh";
|
|
22
|
+
const RTK_DOCS = "https://www.rtk-ai.app/docs/getting-started/installation/";
|
|
23
|
+
const LP_INSTALL_SH = "curl -fsSL https://pkg.lightpanda.io/install.sh | bash";
|
|
24
|
+
const RULESYNC_SCHEMA = "rulesync schema doctor: npm run rulesync:doctor";
|
|
25
|
+
const REQUIRED = ["node", "npm", "git", "rtk", "rulesync", "lightpanda", "chromium"];
|
|
26
|
+
|
|
27
|
+
export function nodeMajor(version) {
|
|
28
|
+
const m = String(version || "").match(/(\d+)/);
|
|
29
|
+
return m ? Number(m[1]) : 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function whichBin(name, pathEnv = process.env.PATH, platform = process.platform) {
|
|
33
|
+
if (!name) return "";
|
|
34
|
+
const delim =
|
|
35
|
+
platform === "win32" && String(pathEnv || "").includes(";") ? ";" : path.delimiter;
|
|
36
|
+
const dirs = String(pathEnv || "").split(delim);
|
|
37
|
+
const names = [name];
|
|
38
|
+
if (platform === "win32" && !path.extname(name)) {
|
|
39
|
+
const pathext = String(process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM");
|
|
40
|
+
for (const raw of pathext.split(";")) {
|
|
41
|
+
if (!raw) continue;
|
|
42
|
+
names.push(name + raw);
|
|
43
|
+
names.push(name + raw.toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
names.push(`${name}.exe`);
|
|
46
|
+
}
|
|
47
|
+
const uniq = [...new Set(names)];
|
|
48
|
+
for (const dir of dirs) {
|
|
49
|
+
if (!dir) continue;
|
|
50
|
+
for (const n of uniq) {
|
|
51
|
+
const candidate = path.join(dir, n);
|
|
52
|
+
try {
|
|
53
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
54
|
+
return candidate;
|
|
55
|
+
} catch {
|
|
56
|
+
if (platform === "win32") {
|
|
57
|
+
try {
|
|
58
|
+
fs.accessSync(candidate, fs.constants.F_OK);
|
|
59
|
+
return candidate;
|
|
60
|
+
} catch {
|
|
61
|
+
/* try next */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function delimOf(pathEnv, platform) {
|
|
71
|
+
return platform === "win32" && String(pathEnv || "").includes(";") ? ";" : path.delimiter;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function withNmBin(cwd, pathEnv, platform) {
|
|
75
|
+
const nm = path.join(cwd || "", "node_modules", ".bin");
|
|
76
|
+
const d = delimOf(pathEnv, platform);
|
|
77
|
+
return `${nm}${d}${pathEnv || ""}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readNodeVersion(bin) {
|
|
81
|
+
try {
|
|
82
|
+
return String(
|
|
83
|
+
execFileSync(bin, ["-v"], {
|
|
84
|
+
encoding: "utf8",
|
|
85
|
+
timeout: 8000,
|
|
86
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
87
|
+
}),
|
|
88
|
+
).trim();
|
|
89
|
+
} catch {
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function lookupChromium(pathEnv, platform) {
|
|
95
|
+
for (const n of CHROMIUM_BINS) {
|
|
96
|
+
const hit = whichBin(n, pathEnv, platform);
|
|
97
|
+
if (hit) return hit;
|
|
98
|
+
}
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function installHints(id, platform = process.platform) {
|
|
103
|
+
if (id === "all") {
|
|
104
|
+
const keys = ["node", "npm", "git", "rtk", "rulesync", "lightpanda", "chromium"];
|
|
105
|
+
return keys.flatMap((k) => installHints(k, platform));
|
|
106
|
+
}
|
|
107
|
+
if (id === "node") {
|
|
108
|
+
if (platform === "win32") {
|
|
109
|
+
return [`${NODE_URL} (≥22)`, "nvm / OS pkg", "winget install OpenJS.NodeJS.LTS"];
|
|
110
|
+
}
|
|
111
|
+
return [`${NODE_URL} (≥22)`, "nvm / OS pkg"];
|
|
112
|
+
}
|
|
113
|
+
if (id === "npm") {
|
|
114
|
+
return ["npm ships with Node ≥22", NODE_URL];
|
|
115
|
+
}
|
|
116
|
+
if (id === "git") {
|
|
117
|
+
if (platform === "win32") return ["winget install Git.Git"];
|
|
118
|
+
if (platform === "darwin") return ["brew install git"];
|
|
119
|
+
return ["sudo apt install git"];
|
|
120
|
+
}
|
|
121
|
+
if (id === "rtk") {
|
|
122
|
+
if (platform === "win32") {
|
|
123
|
+
return ["rtk.exe on PATH (release zip) or WSL curl", RTK_DOCS];
|
|
124
|
+
}
|
|
125
|
+
return [RTK_CURL, "brew install rtk"];
|
|
126
|
+
}
|
|
127
|
+
if (id === "rulesync") {
|
|
128
|
+
return ["npm i -D rulesync", "npx"];
|
|
129
|
+
}
|
|
130
|
+
if (id === "lightpanda") {
|
|
131
|
+
if (platform === "win32") {
|
|
132
|
+
return [
|
|
133
|
+
"Windows: native Lightpanda not required; WSL2 only",
|
|
134
|
+
"wsl --install",
|
|
135
|
+
`in WSL: ${LP_INSTALL_SH}`,
|
|
136
|
+
LIGHTPANDA_REPO,
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
if (platform === "darwin") {
|
|
140
|
+
return [LP_INSTALL_SH, "brew tap lightpanda-io/browser", "brew install lightpanda-io/browser/lightpanda", LIGHTPANDA_REPO];
|
|
141
|
+
}
|
|
142
|
+
return [LP_INSTALL_SH, LIGHTPANDA_REPO];
|
|
143
|
+
}
|
|
144
|
+
if (id === "chromium") {
|
|
145
|
+
if (platform === "win32") {
|
|
146
|
+
return [
|
|
147
|
+
"winget install Google.Chrome",
|
|
148
|
+
"winget install Microsoft.Edge",
|
|
149
|
+
"add chrome.exe or msedge.exe to PATH or shim",
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
if (platform === "darwin") {
|
|
153
|
+
return ["brew install --cask chromium", "brew install --cask google-chrome"];
|
|
154
|
+
}
|
|
155
|
+
return ["sudo apt install chromium"];
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function row(name, status, extra = "") {
|
|
161
|
+
const a = String(name).padEnd(10);
|
|
162
|
+
const b = String(status).padEnd(18);
|
|
163
|
+
const c = extra ? String(extra).trim() : "";
|
|
164
|
+
return c ? `${a} ${b} ${c}` : `${a} ${b}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function runDoctor({
|
|
168
|
+
cwd = process.cwd(),
|
|
169
|
+
pathEnv = process.env.PATH,
|
|
170
|
+
platform = process.platform,
|
|
171
|
+
execPath = process.execPath,
|
|
172
|
+
nodeVersion = process.version,
|
|
173
|
+
} = {}) {
|
|
174
|
+
const nmPath = withNmBin(cwd, pathEnv, platform);
|
|
175
|
+
const pathNode = whichBin("node", pathEnv, platform);
|
|
176
|
+
const nodeBin = pathNode || execPath || "";
|
|
177
|
+
let ver = "";
|
|
178
|
+
if (nodeBin) {
|
|
179
|
+
ver =
|
|
180
|
+
execPath && nodeBin === execPath
|
|
181
|
+
? String(nodeVersion || process.version)
|
|
182
|
+
: readNodeVersion(nodeBin);
|
|
183
|
+
}
|
|
184
|
+
const major = nodeMajor(ver);
|
|
185
|
+
const nodeOk = Boolean(nodeBin) && major >= 22;
|
|
186
|
+
|
|
187
|
+
const npm = whichBin("npm", pathEnv, platform);
|
|
188
|
+
const git = whichBin("git", pathEnv, platform);
|
|
189
|
+
const rtk = whichBin("rtk", nmPath, platform);
|
|
190
|
+
const rulesync = whichBin("rulesync", nmPath, platform);
|
|
191
|
+
const lightpanda = whichBin("lightpanda", pathEnv, platform);
|
|
192
|
+
const chromium = lookupChromium(pathEnv, platform);
|
|
193
|
+
const gh = whichBin("gh", pathEnv, platform);
|
|
194
|
+
|
|
195
|
+
const found = {
|
|
196
|
+
node: nodeOk ? nodeBin : "",
|
|
197
|
+
npm,
|
|
198
|
+
git,
|
|
199
|
+
rtk,
|
|
200
|
+
rulesync,
|
|
201
|
+
lightpanda,
|
|
202
|
+
chromium,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const lines = [];
|
|
206
|
+
if (nodeBin && !nodeOk) {
|
|
207
|
+
lines.push(row("node", "need ≥22", `${ver} (${nodeBin})`));
|
|
208
|
+
} else if (nodeOk) {
|
|
209
|
+
lines.push(row("node", "ok", `${ver} (${nodeBin})`));
|
|
210
|
+
} else {
|
|
211
|
+
lines.push(row("node", "missing"));
|
|
212
|
+
}
|
|
213
|
+
lines.push(npm ? row("npm", "ok", npm) : row("npm", "missing"));
|
|
214
|
+
lines.push(git ? row("git", "ok", git) : row("git", "missing"));
|
|
215
|
+
lines.push(rtk ? row("rtk", "ok", rtk) : row("rtk", "missing"));
|
|
216
|
+
lines.push(rulesync ? row("rulesync", "ok", rulesync) : row("rulesync", "missing"));
|
|
217
|
+
lines.push(lightpanda ? row("lightpanda", "ok", lightpanda) : row("lightpanda", "missing"));
|
|
218
|
+
lines.push(chromium ? row("chromium", "ok", chromium) : row("chromium", "missing"));
|
|
219
|
+
lines.push(gh ? row("gh", "ok", gh) : row("gh", "missing (optional)"));
|
|
220
|
+
|
|
221
|
+
const missingRequired = REQUIRED.filter((k) => (k === "node" ? !nodeOk : !found[k]));
|
|
222
|
+
if (missingRequired.length) {
|
|
223
|
+
lines.push("");
|
|
224
|
+
lines.push("install (print-only; not run):");
|
|
225
|
+
const seen = new Set();
|
|
226
|
+
for (const k of missingRequired) {
|
|
227
|
+
for (const h of installHints(k, platform)) {
|
|
228
|
+
if (seen.has(h)) continue;
|
|
229
|
+
seen.add(h);
|
|
230
|
+
lines.push(h);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
lines.push("");
|
|
236
|
+
lines.push(RULESYNC_SCHEMA);
|
|
237
|
+
const stdout = `${lines.join("\n")}\n`;
|
|
238
|
+
return { code: missingRequired.length ? 1 : 0, stdout, stderr: "" };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function main() {
|
|
242
|
+
const r = runDoctor();
|
|
243
|
+
if (r.stdout) process.stdout.write(r.stdout);
|
|
244
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
245
|
+
process.exit(r.code);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
249
|
+
const invoked = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
|
250
|
+
if (invoked === thisFile || import.meta.url === pathToFileURL(invoked).href) {
|
|
251
|
+
main();
|
|
252
|
+
}
|
|
@@ -510,7 +510,20 @@ function loadInProgressPlans(workspaceRoot) {
|
|
|
510
510
|
return out;
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
-
function
|
|
513
|
+
function specTextOf(data) {
|
|
514
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return "";
|
|
515
|
+
const parts = [];
|
|
516
|
+
for (const k of ["prompt", "userPrompt", "user_prompt", "content"]) {
|
|
517
|
+
if (typeof data[k] === "string" && data[k]) parts.push(data[k]);
|
|
518
|
+
}
|
|
519
|
+
const agent = data.agent;
|
|
520
|
+
if (agent && typeof agent === "object" && !Array.isArray(agent)) {
|
|
521
|
+
if (typeof agent.prompt === "string" && agent.prompt) parts.push(agent.prompt);
|
|
522
|
+
}
|
|
523
|
+
return parts.join("\n");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function implementerWriteDeny(input, workspaceRoot, data) {
|
|
514
527
|
const ws = path.resolve(workspaceRoot);
|
|
515
528
|
const plans = loadInProgressPlans(ws);
|
|
516
529
|
const allow = new Set();
|
|
@@ -520,14 +533,16 @@ function implementerWriteDeny(input, workspaceRoot) {
|
|
|
520
533
|
allow.add(extracted);
|
|
521
534
|
}
|
|
522
535
|
}
|
|
523
|
-
const
|
|
536
|
+
const promptPaths = extractPlanPaths(specTextOf(data), ws);
|
|
537
|
+
for (const abs of promptPaths) allow.add(abs);
|
|
538
|
+
const restrictive = promptPaths.size > 0;
|
|
524
539
|
for (const key of WRITE_PATH_FIELDS) {
|
|
525
540
|
if (typeof input[key] !== "string" || !input[key]) continue;
|
|
526
541
|
const abs = resolveWriteAbs(input[key], ws);
|
|
527
542
|
if (!abs) continue;
|
|
528
543
|
if (isUnderPlansDir(abs, ws)) continue;
|
|
529
544
|
if (allow.has(abs)) continue;
|
|
530
|
-
if (isUnsolicitedDocPath(abs) ||
|
|
545
|
+
if (isUnsolicitedDocPath(abs) || restrictive) {
|
|
531
546
|
return { type: "deny", reason: REASON_IMPLEMENTER_WRITE };
|
|
532
547
|
}
|
|
533
548
|
}
|
|
@@ -556,7 +571,7 @@ export function processFatTools(data) {
|
|
|
556
571
|
}
|
|
557
572
|
}
|
|
558
573
|
if (subagentTypeOf(data) === "implementer") {
|
|
559
|
-
const denied = implementerWriteDeny(next, ws);
|
|
574
|
+
const denied = implementerWriteDeny(next, ws, data);
|
|
560
575
|
if (denied) return denied;
|
|
561
576
|
}
|
|
562
577
|
return changed ? { type: "rewrite", updatedInput: next } : null;
|