@lovrozagar/grunt 0.4.2 → 0.5.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.
Files changed (48) hide show
  1. package/.agents/agents/orchestrator.md +8 -7
  2. package/.agents/agents/thinker.md +6 -5
  3. package/.agents/skills/commit-and-push/SKILL.md +3 -3
  4. package/.agents/skills/explain/SKILL.md +1 -0
  5. package/.agents/skills/implement-plan/SKILL.md +116 -0
  6. package/.agents/skills/write-plan/SKILL.md +82 -0
  7. package/.claude/agents/orchestrator.md +8 -7
  8. package/.claude/agents/thinker.md +6 -5
  9. package/.claude/skills/commit-and-push/SKILL.md +3 -3
  10. package/.claude/skills/explain/SKILL.md +1 -0
  11. package/.claude/skills/implement-plan/SKILL.md +116 -0
  12. package/.claude/skills/write-plan/SKILL.md +82 -0
  13. package/.codex/agents/orchestrator.toml +8 -7
  14. package/.codex/agents/thinker.toml +6 -5
  15. package/.grok/agents/orchestrator.md +8 -7
  16. package/.grok/agents/thinker.md +6 -5
  17. package/.grok/skills/commit-and-push/SKILL.md +3 -3
  18. package/.grok/skills/explain/SKILL.md +1 -0
  19. package/.grok/skills/implement-plan/SKILL.md +45 -17
  20. package/.grok/skills/write-plan/SKILL.md +11 -11
  21. package/.rulesync/reference/INDEX.md +43 -0
  22. package/.rulesync/reference/cascade.md +11 -9
  23. package/.rulesync/reference/law.md +9 -0
  24. package/.rulesync/reference/map.md +10 -5
  25. package/.rulesync/reference/output.md +13 -8
  26. package/{.grok/skills/shared → .rulesync/reference}/plan-format.md +2 -1
  27. package/.rulesync/reference/refs-map.md +14 -0
  28. package/.rulesync/reference/skills-map.md +20 -0
  29. package/.rulesync/rules/CLAUDE.md +12 -10
  30. package/.rulesync/rules/overview.md +12 -10
  31. package/.rulesync/skills/commit-and-push/SKILL.md +3 -3
  32. package/.rulesync/skills/explain/SKILL.md +1 -0
  33. package/.rulesync/skills/implement-plan/SKILL.md +124 -0
  34. package/.rulesync/skills/write-plan/SKILL.md +88 -0
  35. package/.rulesync/subagents/orchestrator.md +8 -7
  36. package/.rulesync/subagents/thinker.md +6 -5
  37. package/AGENTS.md +12 -10
  38. package/CHANGELOG.md +23 -0
  39. package/CLAUDE.md +12 -10
  40. package/README.md +14 -10
  41. package/cli/init.mjs +20 -1
  42. package/cli/init.test.ts +81 -4
  43. package/package.json +4 -4
  44. package/scripts/doctor.mjs +64 -2
  45. package/scripts/emit-maps.mjs +422 -0
  46. package/scripts/guarded-roots.mjs +4 -7
  47. package/scripts/pipeline.mjs +67 -0
  48. package/scripts/skill-conflicts.mjs +71 -0
package/cli/init.mjs CHANGED
@@ -2,6 +2,11 @@ import { execFileSync } from "node:child_process"
2
2
  import fs from "node:fs"
3
3
  import path from "node:path"
4
4
  import { fileURLToPath } from "node:url"
5
+ import {
6
+ WORKSPACE_SKILLS_REL,
7
+ findSkillContentConflicts,
8
+ formatSkillConflictWarn,
9
+ } from "../scripts/skill-conflicts.mjs"
5
10
 
6
11
  const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
7
12
 
@@ -9,10 +14,12 @@ const PRODUCT_SCRIPTS = [
9
14
  "check-globals.mjs",
10
15
  "emit-agent-shell-tools.mjs",
11
16
  "emit-gemini.mjs",
17
+ "emit-maps.mjs",
12
18
  "guarded-roots.mjs",
13
19
  "emit-mcp-policy.mjs",
14
20
  "gate-fat-tools.mjs",
15
21
  "hooks-union.mjs",
22
+ "pipeline.mjs",
16
23
  "grunt-job.mjs",
17
24
  "parse-need.mjs",
18
25
  "persist-handoff.mjs",
@@ -23,6 +30,7 @@ const PRODUCT_SCRIPTS = [
23
30
  "sync-global-settings.mjs",
24
31
  "browser.mjs",
25
32
  "doctor.mjs",
33
+ "skill-conflicts.mjs",
26
34
  "scrub-text",
27
35
  ]
28
36
 
@@ -377,7 +385,7 @@ export function mergePackageJson(dest, pkgRoot) {
377
385
  const srcPkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"))
378
386
  destPkg.scripts = { ...(destPkg.scripts || {}) }
379
387
  for (const [k, v] of Object.entries(srcPkg.scripts || {})) {
380
- if (k === "test") continue
388
+ if (k === "test" || k.endsWith(":raw")) continue
381
389
  destPkg.scripts[k] = mergeScriptValue(k, v, destPkg.scripts[k])
382
390
  }
383
391
  destPkg.devDependencies = { ...(destPkg.devDependencies || {}) }
@@ -406,6 +414,17 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
406
414
  const self = samePath(dest, pkgRoot)
407
415
 
408
416
  phase("merge", () => {
417
+ const wsSkills = path.join(dest, WORKSPACE_SKILLS_REL)
418
+ const pkgSkills = path.join(pkgRoot, WORKSPACE_SKILLS_REL)
419
+ if (!samePath(wsSkills, pkgSkills)) {
420
+ for (const c of findSkillContentConflicts({
421
+ workspaceSkillsDir: wsSkills,
422
+ packagedSkillsDir: pkgSkills,
423
+ })) {
424
+ console.warn(formatSkillConflictWarn(c.name))
425
+ }
426
+ }
427
+
409
428
  for (const dir of COPY_DIRS) {
410
429
  const src = path.join(pkgRoot, dir)
411
430
  const d = path.join(dest, dir)
package/cli/init.test.ts CHANGED
@@ -44,10 +44,12 @@ const PRODUCT_FILES = [
44
44
  "check-globals.mjs",
45
45
  "emit-agent-shell-tools.mjs",
46
46
  "emit-gemini.mjs",
47
+ "emit-maps.mjs",
47
48
  "guarded-roots.mjs",
48
49
  "emit-mcp-policy.mjs",
49
50
  "gate-fat-tools.mjs",
50
51
  "hooks-union.mjs",
52
+ "pipeline.mjs",
51
53
  "grunt-job.mjs",
52
54
  "parse-need.mjs",
53
55
  "persist-handoff.mjs",
@@ -58,6 +60,7 @@ const PRODUCT_FILES = [
58
60
  "sync-global-settings.mjs",
59
61
  "browser.mjs",
60
62
  "doctor.mjs",
63
+ "skill-conflicts.mjs",
61
64
  ];
62
65
 
63
66
  const SRC_CLAUDE_SETTINGS = {
@@ -495,8 +498,15 @@ describe("snapshot/remerge guarded roots", () => {
495
498
  fs.writeFileSync(path.join(dest, "AGENTS.md"), "fresh ssot\n");
496
499
  });
497
500
  runGuardedRoots("generate", { cwd: dest, exec });
498
- expect(exec).toHaveBeenCalledTimes(1);
499
- expect(String(exec.mock.calls[0][0])).toContain("rulesync:generate:raw");
501
+ expect(exec.mock.calls.map((c) => c[0])).toEqual([
502
+ "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills",
503
+ "rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks",
504
+ "node scripts/emit-mcp-policy.mjs",
505
+ "node scripts/emit-gemini.mjs",
506
+ "node scripts/emit-agent-shell-tools.mjs",
507
+ "node scripts/emit-maps.mjs",
508
+ "node scripts/hooks-union.mjs",
509
+ ]);
500
510
  expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
501
511
  "<!-- grunt:begin -->\nfresh ssot\n<!-- grunt:end -->\nkeep me\n",
502
512
  );
@@ -595,8 +605,12 @@ describe("snapshot/remerge guarded roots", () => {
595
605
  return stop;
596
606
  },
597
607
  });
598
- expect(exec).toHaveBeenCalledTimes(1);
599
- expect(String(exec.mock.calls[0][0])).toContain("rulesync:watch:raw");
608
+ expect(exec.mock.calls.map((c) => c[0])).toEqual([
609
+ "node scripts/emit-mcp-policy.mjs",
610
+ "node scripts/emit-gemini.mjs",
611
+ "node scripts/emit-agent-shell-tools.mjs",
612
+ "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills --watch",
613
+ ]);
600
614
  expect(stop).toHaveBeenCalledTimes(1);
601
615
  expect(fs.readFileSync(path.join(dest, "AGENTS.md"), "utf8")).toBe(
602
616
  "<!-- grunt:begin -->\nwatch2\n<!-- grunt:end -->\nkeep me\n",
@@ -952,6 +966,45 @@ describe("mergePackageJson", () => {
952
966
  warnSpy.mockRestore();
953
967
  });
954
968
 
969
+ it("does not merge :raw scripts from src", () => {
970
+ const pkgRoot = stubPkgRoot({
971
+ name: "fixture-pkg",
972
+ scripts: {
973
+ "rulesync:generate": "node ./scripts/guarded-roots.mjs generate",
974
+ "rulesync:generate:raw": "echo raw",
975
+ "rulesync:check:raw": "echo check-raw",
976
+ "rulesync:watch:raw": "echo watch-raw",
977
+ },
978
+ devDependencies: { "smol-toml": "^1.8.0", rulesync: "latest" },
979
+ });
980
+ const dest = tmp("pj-raw-src-");
981
+ mergePackageJson(dest, pkgRoot);
982
+ const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
983
+ expect(out.scripts["rulesync:generate"]).toBe("node ./scripts/guarded-roots.mjs generate");
984
+ expect(out.scripts["rulesync:generate:raw"]).toBeUndefined();
985
+ expect(out.scripts["rulesync:check:raw"]).toBeUndefined();
986
+ expect(out.scripts["rulesync:watch:raw"]).toBeUndefined();
987
+ });
988
+
989
+ it("leaves dest-only :raw keys", () => {
990
+ const pkgRoot = stubPkgRoot({
991
+ name: "fixture-pkg",
992
+ scripts: { "rulesync:generate": "node ./scripts/guarded-roots.mjs generate" },
993
+ devDependencies: { "smol-toml": "^1.8.0", rulesync: "latest" },
994
+ });
995
+ const dest = tmp("pj-raw-dest-");
996
+ fs.writeFileSync(
997
+ path.join(dest, "package.json"),
998
+ JSON.stringify({
999
+ scripts: { "rulesync:generate:raw": "echo consumer-raw" },
1000
+ }),
1001
+ );
1002
+ mergePackageJson(dest, pkgRoot);
1003
+ const out = JSON.parse(fs.readFileSync(path.join(dest, "package.json"), "utf8"));
1004
+ expect(out.scripts["rulesync:generate"]).toBe("node ./scripts/guarded-roots.mjs generate");
1005
+ expect(out.scripts["rulesync:generate:raw"]).toBe("echo consumer-raw");
1006
+ });
1007
+
955
1008
  it("replaces a grunt-owned script with no suffix", () => {
956
1009
  const pkgRoot = stubPkgRoot({
957
1010
  name: "fixture-pkg",
@@ -1262,6 +1315,30 @@ describe("init", () => {
1262
1315
  ["merge", "stop"],
1263
1316
  ]);
1264
1317
  });
1318
+
1319
+ it("warns when dest skill differs from packaged then force-overwrites; keeps extras", () => {
1320
+ const pkgRoot = stubPkgRoot();
1321
+ const pkgSkill = path.join(pkgRoot, ".rulesync", "skills", "parent");
1322
+ fs.mkdirSync(pkgSkill, { recursive: true });
1323
+ fs.writeFileSync(path.join(pkgSkill, "SKILL.md"), "grunt-parent\n");
1324
+ const dest = tmp("grunt-sk-conflict-");
1325
+ const destSkill = path.join(dest, ".rulesync", "skills", "parent");
1326
+ const destExtra = path.join(dest, ".rulesync", "skills", "my-extra");
1327
+ fs.mkdirSync(destSkill, { recursive: true });
1328
+ fs.mkdirSync(destExtra, { recursive: true });
1329
+ fs.writeFileSync(path.join(destSkill, "SKILL.md"), "custom-parent\n");
1330
+ fs.writeFileSync(path.join(destExtra, "SKILL.md"), "keep-me\n");
1331
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
1332
+ init(dest, { pkgRoot, execFileSync: vi.fn(), skipGlobals: true });
1333
+ expect(
1334
+ warnSpy.mock.calls.some(
1335
+ (c) => String(c[0]).includes("parent") && String(c[0]).includes("re-init overwrites"),
1336
+ ),
1337
+ ).toBe(true);
1338
+ warnSpy.mockRestore();
1339
+ expect(fs.readFileSync(path.join(destSkill, "SKILL.md"), "utf8")).toBe("grunt-parent\n");
1340
+ expect(fs.readFileSync(path.join(destExtra, "SKILL.md"), "utf8")).toBe("keep-me\n");
1341
+ });
1265
1342
  });
1266
1343
 
1267
1344
  describe("destAlreadyInited / shouldAutoSkipGlobals", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovrozagar/grunt",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Multi-agent orchestrator SoT — deep-merge rulesync trees, product scripts, and generate pipeline into a consumer repo",
5
5
  "keywords": [
6
6
  "agents",
@@ -27,10 +27,12 @@
27
27
  "scripts/check-globals.mjs",
28
28
  "scripts/emit-agent-shell-tools.mjs",
29
29
  "scripts/emit-gemini.mjs",
30
+ "scripts/emit-maps.mjs",
30
31
  "scripts/guarded-roots.mjs",
31
32
  "scripts/emit-mcp-policy.mjs",
32
33
  "scripts/gate-fat-tools.mjs",
33
34
  "scripts/hooks-union.mjs",
35
+ "scripts/pipeline.mjs",
34
36
  "scripts/grunt-job.mjs",
35
37
  "scripts/parse-need.mjs",
36
38
  "scripts/persist-handoff.mjs",
@@ -41,6 +43,7 @@
41
43
  "scripts/sync-global-settings.mjs",
42
44
  "scripts/browser.mjs",
43
45
  "scripts/doctor.mjs",
46
+ "scripts/skill-conflicts.mjs",
44
47
  "scripts/scrub-text",
45
48
  ".rulesync",
46
49
  ".grok",
@@ -63,11 +66,8 @@
63
66
  },
64
67
  "scripts": {
65
68
  "rulesync:generate": "node ./scripts/guarded-roots.mjs generate",
66
- "rulesync:generate:raw": "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills && rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks && node scripts/emit-mcp-policy.mjs && node scripts/emit-gemini.mjs && node scripts/emit-agent-shell-tools.mjs && node scripts/hooks-union.mjs",
67
69
  "rulesync:watch": "node ./scripts/guarded-roots.mjs watch",
68
- "rulesync:watch:raw": "node scripts/emit-mcp-policy.mjs && node scripts/emit-gemini.mjs && node scripts/emit-agent-shell-tools.mjs && rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills --watch",
69
70
  "rulesync:check": "node ./scripts/guarded-roots.mjs check",
70
- "rulesync:check:raw": "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,skills --check && rulesync generate -t codexcli,antigravity-cli,grokcli -f subagents --check && rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks --check && node scripts/emit-mcp-policy.mjs --check && node scripts/emit-gemini.mjs --check && node scripts/emit-agent-shell-tools.mjs --check && node scripts/check-globals.mjs && node scripts/hooks-union.mjs --check",
71
71
  "hooks:union": "node scripts/hooks-union.mjs",
72
72
  "hooks:check": "node scripts/hooks-union.mjs --check",
73
73
  "doctor": "node ./scripts/doctor.mjs",
@@ -4,6 +4,12 @@ import { execFileSync } from "node:child_process";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import {
8
+ PACKAGED_SKILLS_REL,
9
+ WORKSPACE_SKILLS_REL,
10
+ findSkillContentConflicts,
11
+ formatSkillConflictWarn,
12
+ } from "./skill-conflicts.mjs";
7
13
 
8
14
  export const CHROMIUM_BINS = [
9
15
  "chromium",
@@ -23,6 +29,12 @@ const RTK_DOCS = "https://www.rtk-ai.app/docs/getting-started/installation/";
23
29
  const LP_INSTALL_SH = "curl -fsSL https://pkg.lightpanda.io/install.sh | bash";
24
30
  const RULESYNC_SCHEMA = "rulesync schema doctor: npm run rulesync:doctor";
25
31
  const REQUIRED = ["node", "npm", "git", "rtk", "rulesync", "lightpanda", "chromium"];
32
+ export const REQUIRED_MAP_FILES = [
33
+ { name: "INDEX.md", rel: ".rulesync/reference/INDEX.md" },
34
+ { name: "skills-map.md", rel: ".rulesync/reference/skills-map.md" },
35
+ { name: "refs-map.md", rel: ".rulesync/reference/refs-map.md" },
36
+ { name: "law.md", rel: ".rulesync/reference/law.md" },
37
+ ];
26
38
 
27
39
  export function nodeMajor(version) {
28
40
  const m = String(version || "").match(/(\d+)/);
@@ -158,12 +170,35 @@ export function installHints(id, platform = process.platform) {
158
170
  }
159
171
 
160
172
  function row(name, status, extra = "") {
161
- const a = String(name).padEnd(10);
173
+ const a = String(name).padEnd(14);
162
174
  const b = String(status).padEnd(18);
163
175
  const c = extra ? String(extra).trim() : "";
164
176
  return c ? `${a} ${b} ${c}` : `${a} ${b}`;
165
177
  }
166
178
 
179
+ function mapFilePresent(cwd, rel) {
180
+ try {
181
+ return fs.statSync(path.join(cwd, rel)).isFile();
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+
187
+ function isDir(abs) {
188
+ try {
189
+ return fs.statSync(abs).isDirectory();
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+
195
+ /** Product SoT: reference tree or skills (emit-maps in play). No .rulesync / no those trees → bins-only. */
196
+ export function mapsRequired(cwd) {
197
+ const rs = path.join(cwd || "", ".rulesync");
198
+ if (!isDir(rs)) return false;
199
+ return isDir(path.join(rs, "reference")) || isDir(path.join(rs, "skills"));
200
+ }
201
+
167
202
  export function runDoctor({
168
203
  cwd = process.cwd(),
169
204
  pathEnv = process.env.PATH,
@@ -232,10 +267,37 @@ export function runDoctor({
232
267
  }
233
268
  }
234
269
 
270
+ const missingMaps = [];
271
+ if (mapsRequired(cwd)) {
272
+ for (const f of REQUIRED_MAP_FILES) {
273
+ if (mapFilePresent(cwd, f.rel)) {
274
+ lines.push(row(f.name, "ok", f.rel));
275
+ } else {
276
+ missingMaps.push(f);
277
+ lines.push(row(f.name, "missing", f.rel));
278
+ }
279
+ }
280
+ if (missingMaps.length) {
281
+ lines.push(`maps missing: ${missingMaps.map((f) => f.name).join(", ")}`);
282
+ }
283
+ }
284
+
285
+ const skillConflicts = findSkillContentConflicts({
286
+ workspaceSkillsDir: path.join(cwd || "", WORKSPACE_SKILLS_REL),
287
+ packagedSkillsDir: path.join(cwd || "", PACKAGED_SKILLS_REL),
288
+ });
289
+ if (skillConflicts.length) {
290
+ lines.push("");
291
+ lines.push("skill conflicts (warn; re-init overwrites grunt-owned names):");
292
+ for (const c of skillConflicts) {
293
+ lines.push(formatSkillConflictWarn(c.name));
294
+ }
295
+ }
296
+
235
297
  lines.push("");
236
298
  lines.push(RULESYNC_SCHEMA);
237
299
  const stdout = `${lines.join("\n")}\n`;
238
- return { code: missingRequired.length ? 1 : 0, stdout, stderr: "" };
300
+ return { code: missingRequired.length || missingMaps.length ? 1 : 0, stdout, stderr: "" };
239
301
  }
240
302
 
241
303
  function main() {