@lovrozagar/grunt 0.1.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 +36 -0
- package/.agents/agents/implementer.md +40 -0
- package/.agents/agents/orchestrator.md +50 -0
- package/.agents/agents/thinker.md +27 -0
- package/.agents/hooks.json +26 -0
- package/.agents/mcp_config.json +3 -0
- package/.claude/agents/grunt.md +44 -0
- package/.claude/agents/implementer.md +46 -0
- package/.claude/agents/orchestrator.md +55 -0
- package/.claude/agents/thinker.md +34 -0
- package/.claude/settings.json +36 -0
- package/.codex/agents/grunt.toml +30 -0
- package/.codex/agents/implementer.toml +34 -0
- package/.codex/agents/orchestrator.toml +45 -0
- package/.codex/agents/thinker.toml +23 -0
- package/.codex/config.toml +2 -0
- package/.codex/hooks.json +26 -0
- package/.grok/agents/grunt.md +31 -0
- package/.grok/agents/implementer.md +35 -0
- package/.grok/agents/orchestrator.md +47 -0
- package/.grok/agents/thinker.md +25 -0
- package/.grok/config.toml +6 -0
- package/.grok/global-settings.toml +35 -0
- package/.grok/hooks/orchestrate-parent.js +476 -0
- package/.grok/hooks/orchestrate-parent.json +60 -0
- package/.grok/hooks/rtk-grok.js +137 -0
- package/.grok/hooks/rtk.json +16 -0
- package/.grok/hooks/rulesync.json +3 -0
- package/.grok/parent.md +1 -0
- package/.grok/roles/grunt.toml +5 -0
- package/.grok/roles/implementer.toml +5 -0
- package/.grok/roles/thinker.toml +5 -0
- package/.grok/skills/explain/SKILL.md +15 -0
- package/.grok/skills/implement-plan/SKILL.md +91 -0
- package/.grok/skills/parent/SKILL.md +12 -0
- package/.grok/skills/shared/plan-format.md +158 -0
- package/.grok/skills/write-plan/SKILL.md +84 -0
- package/.mcp.json +3 -0
- package/.rulesync/global-settings/antigravity.json +1 -0
- package/.rulesync/global-settings/claude.json +1 -0
- package/.rulesync/global-settings/codex.toml +1 -0
- package/.rulesync/global-settings/manifest.json +37 -0
- package/.rulesync/hooks.jsonc +22 -0
- package/.rulesync/mcp-policy.jsonc +15 -0
- package/.rulesync/reference/cascade.md +85 -0
- package/.rulesync/reference/hooks.md +19 -0
- package/.rulesync/reference/map.md +57 -0
- package/.rulesync/reference/rtk.md +23 -0
- package/.rulesync/rules/CLAUDE.md +13 -0
- package/.rulesync/rules/overview.md +23 -0
- package/.rulesync/skills/explain/SKILL.md +15 -0
- package/.rulesync/skills/parent/SKILL.md +12 -0
- package/.rulesync/skills/terse/SKILL.md +8 -0
- package/.rulesync/subagents/grunt.md +51 -0
- package/.rulesync/subagents/implementer.md +54 -0
- package/.rulesync/subagents/orchestrator.md +69 -0
- package/.rulesync/subagents/thinker.md +44 -0
- package/AGENTS.md +12 -0
- package/CLAUDE.md +7 -0
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/bin/grunt.js +4 -0
- package/cli/grunt.mjs +54 -0
- package/cli/grunt.test.ts +117 -0
- package/cli/init.mjs +113 -0
- package/cli/init.test.ts +268 -0
- package/package.json +77 -0
- package/scripts/check-globals.mjs +98 -0
- package/scripts/emit-mcp-policy.mjs +434 -0
- package/scripts/gate-fat-tools.mjs +485 -0
- package/scripts/grunt-job.mjs +482 -0
- package/scripts/parse-need.mjs +75 -0
- package/scripts/persist-plan.mjs +309 -0
- package/scripts/purge-global-mcps.mjs +291 -0
- package/scripts/scrub-spawn-prompt.mjs +219 -0
- package/scripts/scrub-text +58 -0
- package/scripts/scrub-text-lib.mjs +210 -0
- package/scripts/sync-global-settings.mjs +239 -0
- package/scripts/telemetry.mjs +38 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Merge repo Grok global settings into $HOME/.grok/config.toml (dry-run default). */
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
7
|
+
|
|
8
|
+
const HOST_IDS = new Set(["grok", "claude", "codex", "antigravity", "all"]);
|
|
9
|
+
const MANIFEST_REL = ".rulesync/global-settings/manifest.json";
|
|
10
|
+
|
|
11
|
+
export function parseArgv(argv) {
|
|
12
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
13
|
+
let apply = false;
|
|
14
|
+
let host = "all";
|
|
15
|
+
for (let i = 0; i < args.length; i++) {
|
|
16
|
+
const a = args[i];
|
|
17
|
+
if (a === "--apply") {
|
|
18
|
+
apply = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (a === "--host") {
|
|
22
|
+
const v = args[++i];
|
|
23
|
+
if (!v || !HOST_IDS.has(v)) {
|
|
24
|
+
return { ok: false, error: `unknown host: ${v || ""}` };
|
|
25
|
+
}
|
|
26
|
+
host = v;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (typeof a === "string" && a.startsWith("--host=")) {
|
|
30
|
+
const v = a.slice("--host=".length);
|
|
31
|
+
if (!HOST_IDS.has(v)) {
|
|
32
|
+
return { ok: false, error: `unknown host: ${v}` };
|
|
33
|
+
}
|
|
34
|
+
host = v;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
return { ok: false, error: `unknown flag: ${a}` };
|
|
38
|
+
}
|
|
39
|
+
return { ok: true, apply, host };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveDest(home, dest) {
|
|
43
|
+
if (!home) throw new Error("home required");
|
|
44
|
+
if (dest == null || dest === "") throw new Error("dest required");
|
|
45
|
+
const resolvedHome = path.resolve(home);
|
|
46
|
+
const resolved = path.resolve(resolvedHome, dest);
|
|
47
|
+
const prefix = resolvedHome.endsWith(path.sep)
|
|
48
|
+
? resolvedHome
|
|
49
|
+
: resolvedHome + path.sep;
|
|
50
|
+
if (resolved !== resolvedHome && !resolved.startsWith(prefix)) {
|
|
51
|
+
throw new Error(`dest not under home: ${dest}`);
|
|
52
|
+
}
|
|
53
|
+
return resolved;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function shouldSkip(segment, skipRe) {
|
|
57
|
+
if (!skipRe) return false;
|
|
58
|
+
return skipRe.test(String(segment));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isPlainObject(v) {
|
|
62
|
+
return v != null && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function deepMerge(dest, source, skipRe, keyPath = []) {
|
|
66
|
+
const out = isPlainObject(dest) ? { ...dest } : {};
|
|
67
|
+
if (!isPlainObject(source)) return out;
|
|
68
|
+
for (const [k, v] of Object.entries(source)) {
|
|
69
|
+
const nextPath = [...keyPath, k];
|
|
70
|
+
if (nextPath.some((seg) => shouldSkip(seg, skipRe))) continue;
|
|
71
|
+
const prev = out[k];
|
|
72
|
+
if (isPlainObject(v) && isPlainObject(prev)) {
|
|
73
|
+
out[k] = deepMerge(prev, v, skipRe, nextPath);
|
|
74
|
+
} else {
|
|
75
|
+
out[k] = v;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function loadManifest(workspaceRoot) {
|
|
82
|
+
const p = path.join(workspaceRoot, MANIFEST_REL);
|
|
83
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
84
|
+
const manifest = JSON.parse(raw);
|
|
85
|
+
if (!manifest || typeof manifest !== "object") {
|
|
86
|
+
throw new Error("invalid manifest");
|
|
87
|
+
}
|
|
88
|
+
if (typeof manifest.skipKeyPattern !== "string") {
|
|
89
|
+
throw new Error("manifest skipKeyPattern required");
|
|
90
|
+
}
|
|
91
|
+
if (!Array.isArray(manifest.hosts)) {
|
|
92
|
+
throw new Error("manifest hosts required");
|
|
93
|
+
}
|
|
94
|
+
return manifest;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseTomlText(raw, label) {
|
|
98
|
+
const s = String(raw ?? "");
|
|
99
|
+
if (!s.trim()) return {};
|
|
100
|
+
try {
|
|
101
|
+
const v = parseToml(s);
|
|
102
|
+
return isPlainObject(v) ? v : {};
|
|
103
|
+
} catch (err) {
|
|
104
|
+
throw new Error(`unreadable dest: ${label}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readTomlDest(destAbs, label) {
|
|
109
|
+
let raw;
|
|
110
|
+
try {
|
|
111
|
+
raw = fs.readFileSync(destAbs, "utf8");
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err && err.code === "ENOENT") return {};
|
|
114
|
+
throw new Error(`unreadable dest: ${label}`);
|
|
115
|
+
}
|
|
116
|
+
return parseTomlText(raw, label);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function reportLine({ action, id, mode, dest }) {
|
|
120
|
+
const destCol = mode === "noop" || !dest ? "noop" : dest;
|
|
121
|
+
return `${action} ${id} ${mode} ${destCol}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function syncGlobals({
|
|
125
|
+
workspaceRoot,
|
|
126
|
+
home,
|
|
127
|
+
apply = false,
|
|
128
|
+
host = "all",
|
|
129
|
+
} = {}) {
|
|
130
|
+
const report = [];
|
|
131
|
+
const action = apply ? "apply" : "dry-run";
|
|
132
|
+
try {
|
|
133
|
+
const ws = workspaceRoot || process.cwd();
|
|
134
|
+
if (!HOST_IDS.has(host)) {
|
|
135
|
+
return { ok: false, report, error: `unknown host: ${host}` };
|
|
136
|
+
}
|
|
137
|
+
const manifest = loadManifest(ws);
|
|
138
|
+
const skipRe = new RegExp(manifest.skipKeyPattern, "i");
|
|
139
|
+
const hosts = manifest.hosts.filter((h) => host === "all" || h.id === host);
|
|
140
|
+
if (!hosts.length) {
|
|
141
|
+
return { ok: false, report, error: `unknown host: ${host}` };
|
|
142
|
+
}
|
|
143
|
+
for (const h of hosts) {
|
|
144
|
+
const payloadRel = h.payload;
|
|
145
|
+
const payloadPath = path.join(ws, payloadRel);
|
|
146
|
+
if (!fs.existsSync(payloadPath)) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
report,
|
|
150
|
+
error: `missing payload: ${payloadRel}`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (h.mode === "noop") {
|
|
154
|
+
report.push({
|
|
155
|
+
action,
|
|
156
|
+
id: h.id,
|
|
157
|
+
mode: "noop",
|
|
158
|
+
dest: "noop",
|
|
159
|
+
line: reportLine({ action, id: h.id, mode: "noop", dest: "noop" }),
|
|
160
|
+
});
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const destAbs = resolveDest(home, h.dest);
|
|
164
|
+
const sourceRaw = fs.readFileSync(payloadPath, "utf8");
|
|
165
|
+
let source;
|
|
166
|
+
try {
|
|
167
|
+
source = parseTomlText(sourceRaw, payloadRel);
|
|
168
|
+
} catch {
|
|
169
|
+
return { ok: false, report, error: `unreadable payload: ${payloadRel}` };
|
|
170
|
+
}
|
|
171
|
+
const destObj = fs.existsSync(destAbs)
|
|
172
|
+
? readTomlDest(destAbs, h.dest)
|
|
173
|
+
: {};
|
|
174
|
+
const merged = deepMerge(destObj, source, skipRe);
|
|
175
|
+
if (apply) {
|
|
176
|
+
fs.mkdirSync(path.dirname(destAbs), { recursive: true });
|
|
177
|
+
const body = stringifyToml(merged);
|
|
178
|
+
fs.writeFileSync(
|
|
179
|
+
destAbs,
|
|
180
|
+
body.endsWith("\n") ? body : body + "\n",
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
report.push({
|
|
184
|
+
action,
|
|
185
|
+
id: h.id,
|
|
186
|
+
mode: h.mode,
|
|
187
|
+
dest: h.dest,
|
|
188
|
+
line: reportLine({
|
|
189
|
+
action,
|
|
190
|
+
id: h.id,
|
|
191
|
+
mode: h.mode,
|
|
192
|
+
dest: h.dest,
|
|
193
|
+
}),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return { ok: true, report };
|
|
197
|
+
} catch (err) {
|
|
198
|
+
const error = String(err && err.message ? err.message : err);
|
|
199
|
+
return { ok: false, report, error };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function main() {
|
|
204
|
+
try {
|
|
205
|
+
const parsed = parseArgv(process.argv.slice(2));
|
|
206
|
+
if (!parsed.ok) {
|
|
207
|
+
process.stderr.write((parsed.error || "invalid argv") + "\n");
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
const home = process.env.HOME;
|
|
211
|
+
if (!home) {
|
|
212
|
+
process.stderr.write("HOME required\n");
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
const result = syncGlobals({
|
|
216
|
+
workspaceRoot: process.cwd(),
|
|
217
|
+
home,
|
|
218
|
+
apply: parsed.apply,
|
|
219
|
+
host: parsed.host,
|
|
220
|
+
});
|
|
221
|
+
for (const row of result.report || []) {
|
|
222
|
+
process.stdout.write((row.line || reportLine(row)) + "\n");
|
|
223
|
+
}
|
|
224
|
+
if (!result.ok) {
|
|
225
|
+
process.stderr.write((result.error || "sync failed") + "\n");
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
return 0;
|
|
229
|
+
} catch (err) {
|
|
230
|
+
process.stderr.write(String(err && err.message ? err.message : err) + "\n");
|
|
231
|
+
return 1;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
236
|
+
const invoked = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
|
237
|
+
if (invoked === thisFile || import.meta.url === pathToFileURL(invoked).href) {
|
|
238
|
+
process.exit(main());
|
|
239
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Append-only hook sidecar. Never throws. Never writes stdout. */
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const QUERY_MAX = 120;
|
|
7
|
+
|
|
8
|
+
export const ORCHESTRATOR_LOGS_DIR = ".tmp/orchestrator-logs";
|
|
9
|
+
|
|
10
|
+
export function telemetryPath(workspaceRoot) {
|
|
11
|
+
const root =
|
|
12
|
+
workspaceRoot ||
|
|
13
|
+
process.env.GROK_WORKSPACE_ROOT ||
|
|
14
|
+
process.cwd();
|
|
15
|
+
return path.join(root, ORCHESTRATOR_LOGS_DIR, "telemetry.ndjson");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function truncateQuery(q) {
|
|
19
|
+
const s = String(q ?? "");
|
|
20
|
+
if (s.length <= QUERY_MAX) return s;
|
|
21
|
+
return s.slice(0, QUERY_MAX);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function logTelemetry(event, fields, workspaceRoot) {
|
|
25
|
+
try {
|
|
26
|
+
const rec = { ts: Date.now(), event: String(event || "") };
|
|
27
|
+
const src = fields && typeof fields === "object" ? fields : {};
|
|
28
|
+
for (const [k, v] of Object.entries(src)) {
|
|
29
|
+
if (k === "query" && v != null) rec.query = truncateQuery(v);
|
|
30
|
+
else rec[k] = v;
|
|
31
|
+
}
|
|
32
|
+
const dest = telemetryPath(workspaceRoot);
|
|
33
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
34
|
+
fs.appendFileSync(dest, JSON.stringify(rec) + "\n");
|
|
35
|
+
} catch {
|
|
36
|
+
// fail-open
|
|
37
|
+
}
|
|
38
|
+
}
|