@lovrozagar/grunt 0.4.2 → 0.5.2

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 (96) hide show
  1. package/.agents/agents/grunt.md +2 -1
  2. package/.agents/agents/implementer.md +1 -1
  3. package/.agents/agents/orchestrator.md +19 -13
  4. package/.agents/agents/thinker.md +8 -6
  5. package/.agents/skills/ask/SKILL.md +18 -0
  6. package/.agents/skills/auto/SKILL.md +18 -0
  7. package/.agents/skills/browser/SKILL.md +4 -3
  8. package/.agents/skills/cascade/SKILL.md +5 -3
  9. package/.agents/skills/commit-and-push/SKILL.md +3 -3
  10. package/.agents/skills/explain/SKILL.md +1 -0
  11. package/.agents/skills/handoff/SKILL.md +1 -1
  12. package/.agents/skills/implement-plan/SKILL.md +117 -0
  13. package/.agents/skills/solo/SKILL.md +5 -3
  14. package/.agents/skills/tmp/SKILL.md +58 -0
  15. package/.agents/skills/write-plan/SKILL.md +85 -0
  16. package/.claude/agents/grunt.md +2 -1
  17. package/.claude/agents/implementer.md +1 -1
  18. package/.claude/agents/orchestrator.md +19 -13
  19. package/.claude/agents/thinker.md +8 -6
  20. package/.claude/skills/ask/SKILL.md +18 -0
  21. package/.claude/skills/auto/SKILL.md +18 -0
  22. package/.claude/skills/browser/SKILL.md +4 -3
  23. package/.claude/skills/cascade/SKILL.md +5 -3
  24. package/.claude/skills/commit-and-push/SKILL.md +3 -3
  25. package/.claude/skills/explain/SKILL.md +1 -0
  26. package/.claude/skills/handoff/SKILL.md +1 -1
  27. package/.claude/skills/implement-plan/SKILL.md +117 -0
  28. package/.claude/skills/solo/SKILL.md +5 -3
  29. package/.claude/skills/tmp/SKILL.md +58 -0
  30. package/.claude/skills/write-plan/SKILL.md +85 -0
  31. package/.codex/agents/grunt.toml +2 -1
  32. package/.codex/agents/implementer.toml +1 -1
  33. package/.codex/agents/orchestrator.toml +19 -13
  34. package/.codex/agents/thinker.toml +8 -6
  35. package/.grok/agents/grunt.md +2 -1
  36. package/.grok/agents/implementer.md +1 -1
  37. package/.grok/agents/orchestrator.md +19 -13
  38. package/.grok/agents/thinker.md +8 -6
  39. package/.grok/hooks/orchestrate-parent.js +280 -62
  40. package/.grok/skills/ask/SKILL.md +18 -0
  41. package/.grok/skills/auto/SKILL.md +18 -0
  42. package/.grok/skills/browser/SKILL.md +4 -3
  43. package/.grok/skills/cascade/SKILL.md +5 -3
  44. package/.grok/skills/commit-and-push/SKILL.md +3 -3
  45. package/.grok/skills/explain/SKILL.md +1 -0
  46. package/.grok/skills/handoff/SKILL.md +1 -1
  47. package/.grok/skills/implement-plan/SKILL.md +49 -20
  48. package/.grok/skills/solo/SKILL.md +5 -3
  49. package/.grok/skills/tmp/SKILL.md +58 -0
  50. package/.grok/skills/write-plan/SKILL.md +17 -14
  51. package/.rulesync/grunt.config.jsonc +7 -0
  52. package/.rulesync/reference/INDEX.md +46 -0
  53. package/.rulesync/reference/cascade.md +19 -15
  54. package/.rulesync/reference/hooks.md +6 -4
  55. package/.rulesync/reference/law.md +9 -0
  56. package/.rulesync/reference/map.md +20 -10
  57. package/.rulesync/reference/output.md +23 -8
  58. package/{.grok/skills/shared → .rulesync/reference}/plan-format.md +12 -11
  59. package/.rulesync/reference/refs-map.md +14 -0
  60. package/.rulesync/reference/skills-map.md +23 -0
  61. package/.rulesync/rules/CLAUDE.md +22 -15
  62. package/.rulesync/rules/overview.md +22 -15
  63. package/.rulesync/skills/ask/SKILL.md +18 -0
  64. package/.rulesync/skills/auto/SKILL.md +18 -0
  65. package/.rulesync/skills/browser/SKILL.md +2 -2
  66. package/.rulesync/skills/cascade/SKILL.md +5 -3
  67. package/.rulesync/skills/commit-and-push/SKILL.md +3 -3
  68. package/.rulesync/skills/explain/SKILL.md +1 -0
  69. package/.rulesync/skills/handoff/SKILL.md +1 -1
  70. package/.rulesync/skills/implement-plan/SKILL.md +127 -0
  71. package/.rulesync/skills/solo/SKILL.md +5 -3
  72. package/.rulesync/skills/tmp/SKILL.md +58 -0
  73. package/.rulesync/skills/write-plan/SKILL.md +92 -0
  74. package/.rulesync/subagents/grunt.md +2 -1
  75. package/.rulesync/subagents/implementer.md +1 -1
  76. package/.rulesync/subagents/orchestrator.md +19 -13
  77. package/.rulesync/subagents/thinker.md +8 -6
  78. package/AGENTS.md +22 -15
  79. package/CHANGELOG.md +56 -0
  80. package/CLAUDE.md +22 -15
  81. package/README.md +27 -22
  82. package/cli/grunt.mjs +12 -12
  83. package/cli/grunt.test.ts +29 -20
  84. package/cli/init.mjs +135 -22
  85. package/cli/init.test.ts +624 -36
  86. package/package.json +11 -4
  87. package/scripts/doctor.mjs +64 -2
  88. package/scripts/emit-maps.mjs +422 -0
  89. package/scripts/gate-fat-tools.mjs +2 -2
  90. package/scripts/grunt-config.mjs +50 -0
  91. package/scripts/guarded-roots.mjs +4 -7
  92. package/scripts/persist-plan.mjs +17 -2
  93. package/scripts/persist-tmp.mjs +123 -0
  94. package/scripts/pipeline.mjs +67 -0
  95. package/scripts/scrub-spawn-prompt.mjs +1 -1
  96. 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,13 +14,17 @@ 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",
24
+ "grunt-config.mjs",
17
25
  "parse-need.mjs",
18
26
  "persist-handoff.mjs",
27
+ "persist-tmp.mjs",
19
28
  "persist-plan.mjs",
20
29
  "purge-global-mcps.mjs",
21
30
  "scrub-spawn-prompt.mjs",
@@ -23,6 +32,7 @@ const PRODUCT_SCRIPTS = [
23
32
  "sync-global-settings.mjs",
24
33
  "browser.mjs",
25
34
  "doctor.mjs",
35
+ "skill-conflicts.mjs",
26
36
  "scrub-text",
27
37
  ]
28
38
 
@@ -30,14 +40,30 @@ const COPY_DIRS = [".rulesync", ".grok", ".codex", ".claude", ".agents"]
30
40
  const GUARDED_MD_FILES = ["AGENTS.md", "CLAUDE.md"]
31
41
  export const GUARDED_ROOT_FILES = ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]
32
42
  // .mcp.json is intentionally not copied here: scripts/emit-mcp-policy.mjs
33
- // (run via `rulesync:generate`, see mergeClaudeSettings ordering) owns and
43
+ // (run via consumer `grunt:rulesync:generate`, see mergeClaudeSettings ordering) owns and
34
44
  // merges it, so a plain-copy would just be clobbered/redundant.
45
+ export const GRUNT_NPM_PREFIX = "grunt:"
46
+ export function toGruntScriptName(k) {
47
+ return k.startsWith(GRUNT_NPM_PREFIX) ? k : `${GRUNT_NPM_PREFIX}${k}`
48
+ }
49
+ export const LAUNCH_SCRIPTS = {
50
+ antigravity: "antigravity --dangerously-skip-permissions",
51
+ claude: "claude --dangerously-skip-permissions",
52
+ codex: "codex --dangerously-bypass-approvals-and-sandbox",
53
+ gemini: "gemini --yolo",
54
+ grok: "grok --yolo",
55
+ }
35
56
  const OWNED_HOOK_FILES = ["scrub-spawn-prompt.mjs", "gate-fat-tools.mjs", "orchestrate-parent.js"]
36
57
  export const SENTINEL_BEGIN = "<!-- grunt:begin -->"
37
58
  export const SENTINEL_END = "<!-- grunt:end -->"
38
59
  export const MAX_GUARDED_MARKDOWN_BYTES = 2 * 1024 * 1024
39
- const GRUNT_REGION_RE = /<!-- grunt:begin -->\r?\n?[\s\S]*?<!-- grunt:end -->\r?\n?/g
40
- const GRUNT_INTERIOR_RE = /<!-- grunt:begin -->\r?\n?([\s\S]*?)<!-- grunt:end -->/g
60
+ const GRUNT_REGION_RE =
61
+ /(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:begin -->[ \t]*\r?\n?[\s\S]*?(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:end -->[ \t]*\r?\n?/g
62
+ const GRUNT_INTERIOR_RE =
63
+ /(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:begin -->[ \t]*\r?\n?([\s\S]*?)(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:end -->/g
64
+ const ORPHAN_END_RE = /(?:^|\r?\n)[ \t]*<!-- grunt:end -->[ \t]*(?=\r?\n|$)|<!-- grunt:end -->/g
65
+ const BEGIN_LINE_RE = /^[ \t]*<!-- grunt:begin -->[ \t]*$/
66
+ const END_LINE_RE = /^[ \t]*<!-- grunt:end -->[ \t]*$/
41
67
 
42
68
  function sortKeys(obj) {
43
69
  return Object.fromEntries(Object.keys(obj).sort().map((k) => [k, obj[k]]))
@@ -110,18 +136,43 @@ function inspectGuardedBuffer(buf) {
110
136
  }
111
137
 
112
138
  export function extractUserMarkdown(text) {
113
- return String(text).replace(cloneRe(GRUNT_REGION_RE), "")
139
+ let user = String(text).replace(cloneRe(GRUNT_REGION_RE), "")
140
+ const i = user.indexOf(SENTINEL_BEGIN)
141
+ if (i >= 0) user = user.slice(0, i)
142
+ user = user.replace(cloneRe(ORPHAN_END_RE), "")
143
+ return userRemainderIsBlank(user) ? "" : user
144
+ }
145
+
146
+ function unwrapStackedMarkers(text) {
147
+ const lines = String(text).split(/\r?\n/)
148
+ let start = 0
149
+ let end = lines.length
150
+ while (start < end && BEGIN_LINE_RE.test(lines[start])) start++
151
+ while (end > start && lines[end - 1] === "") end--
152
+ while (end > start && END_LINE_RE.test(lines[end - 1])) {
153
+ end--
154
+ while (end > start && lines[end - 1] === "") end--
155
+ }
156
+ return lines.slice(start, end).join("\n")
114
157
  }
115
158
 
116
159
  export function extractGruntBody(text) {
160
+ const src = String(text)
117
161
  const parts = []
118
162
  const re = cloneRe(GRUNT_INTERIOR_RE)
119
163
  let m
120
- while ((m = re.exec(String(text)))) {
121
- parts.push(trimTrailingNewlines(m[1]))
164
+ while ((m = re.exec(src))) {
165
+ parts.push(trimTrailingNewlines(unwrapStackedMarkers(m[1])))
122
166
  }
123
- if (!parts.length) return null
124
- return parts.join("\n")
167
+ if (parts.length) return parts.join("\n")
168
+ const i = src.indexOf(SENTINEL_BEGIN)
169
+ if (i >= 0) {
170
+ return trimTrailingNewlines(
171
+ unwrapStackedMarkers(src.slice(i + SENTINEL_BEGIN.length).replace(/^[ \t]*\r?\n/, "")),
172
+ )
173
+ }
174
+ if (!src.includes(SENTINEL_END)) return null
175
+ return trimTrailingNewlines(unwrapStackedMarkers(src))
125
176
  }
126
177
 
127
178
  function userRemainderIsBlank(user) {
@@ -129,9 +180,10 @@ function userRemainderIsBlank(user) {
129
180
  }
130
181
 
131
182
  export function composeGuardedMarkdown(gruntBody, userRemainder, nl = "\n") {
132
- const body = toNewline(trimTrailingNewlines(gruntBody), nl)
183
+ const raw = extractGruntBody(gruntBody) ?? gruntBody
184
+ const body = toNewline(trimTrailingNewlines(raw), nl)
133
185
  const block = `${SENTINEL_BEGIN}${nl}${body}${nl}${SENTINEL_END}${nl}`
134
- const user = userRemainder == null ? "" : String(userRemainder)
186
+ const user = extractUserMarkdown(userRemainder == null ? "" : String(userRemainder))
135
187
  if (!user || userRemainderIsBlank(user)) return block
136
188
  return block + toNewline(user, nl)
137
189
  }
@@ -176,7 +228,7 @@ export function mergeGuardedMarkdown(dest, pkgRoot, file, { alreadyInited = fals
176
228
  const d = path.join(dest, file)
177
229
  if (samePath(src, d)) return
178
230
  const srcText = fs.readFileSync(src, "utf8")
179
- return writeMergedGuardedFile(d, srcText)
231
+ return writeMergedGuardedFile(d, extractGruntBody(srcText) ?? srcText)
180
232
  }
181
233
 
182
234
  export function snapshotGuardedRoots(root) {
@@ -331,7 +383,13 @@ export function mergeClaudeSettings(destRoot, pkgRoot) {
331
383
  }
332
384
 
333
385
  function looksGruntOwnedPrefix(prefix) {
334
- return /rulesync|sync:globals|^npm run /.test(prefix)
386
+ return /rulesync|sync:globals|^npm run |guarded-roots\.mjs|hooks-union\.mjs|doctor\.mjs|sync-global-settings\.mjs|check-globals\.mjs|purge-global-mcps\.mjs/.test(
387
+ prefix,
388
+ )
389
+ }
390
+
391
+ function escapeRe(s) {
392
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
335
393
  }
336
394
 
337
395
  function commandCount(script) {
@@ -356,14 +414,29 @@ function extraOwnedSuffix(cur, newSrc) {
356
414
  return null
357
415
  }
358
416
 
359
- function mergeScriptValue(name, newSrc, cur) {
360
- if (cur == null) return newSrc
417
+ function mergeScriptValue(newSrc, cur) {
361
418
  if (cur.startsWith(newSrc)) return cur
362
419
  const suffix = extraOwnedSuffix(cur, newSrc)
363
420
  if (suffix != null) return newSrc + suffix
364
- if (looksGruntOwnedPrefix(cur)) return newSrc
365
- console.warn(`script \`${name}\` left untouched (unrelated customization)`)
366
- return cur
421
+ return newSrc
422
+ }
423
+
424
+ function rewriteNpmRunRefs(scripts, srcKeys) {
425
+ const keys = srcKeys
426
+ .filter((k) => k !== toGruntScriptName(k))
427
+ .sort((a, b) => b.length - a.length)
428
+ if (!keys.length) return
429
+ for (const name of Object.keys(scripts)) {
430
+ let val = scripts[name]
431
+ if (typeof val !== "string") continue
432
+ for (const k of keys) {
433
+ val = val.replace(
434
+ new RegExp(`npm run ${escapeRe(k)}(?=$|\\s|;|&)`, "g"),
435
+ `npm run ${toGruntScriptName(k)}`,
436
+ )
437
+ }
438
+ scripts[name] = val
439
+ }
367
440
  }
368
441
 
369
442
  export function mergePackageJson(dest, pkgRoot) {
@@ -376,10 +449,39 @@ export function mergePackageJson(dest, pkgRoot) {
376
449
 
377
450
  const srcPkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"))
378
451
  destPkg.scripts = { ...(destPkg.scripts || {}) }
452
+ const srcKeys = []
379
453
  for (const [k, v] of Object.entries(srcPkg.scripts || {})) {
380
- if (k === "test") continue
381
- destPkg.scripts[k] = mergeScriptValue(k, v, destPkg.scripts[k])
454
+ if (k === "test" || k.endsWith(":raw") || k in LAUNCH_SCRIPTS) continue
455
+ srcKeys.push(k)
456
+ const destKey = toGruntScriptName(k)
457
+ const current = destPkg.scripts[destKey]
458
+ const legacy = destPkg.scripts[k]
459
+ const ownedLegacy =
460
+ legacy != null &&
461
+ (legacy.startsWith(v) || extraOwnedSuffix(legacy, v) != null || looksGruntOwnedPrefix(legacy))
462
+ if (current != null) {
463
+ destPkg.scripts[destKey] = mergeScriptValue(v, current)
464
+ } else if (ownedLegacy) {
465
+ destPkg.scripts[destKey] = mergeScriptValue(v, legacy)
466
+ } else {
467
+ destPkg.scripts[destKey] = v
468
+ }
469
+ if (k !== destKey && ownedLegacy) {
470
+ delete destPkg.scripts[k]
471
+ } else if (k !== destKey && legacy != null) {
472
+ console.warn(
473
+ `script \`${k}\` left untouched (unrelated customization); writing \`${destKey}\``,
474
+ )
475
+ }
382
476
  }
477
+ for (const [k, v] of Object.entries(LAUNCH_SCRIPTS)) {
478
+ const destKey = toGruntScriptName(k)
479
+ const current = destPkg.scripts[destKey]
480
+ destPkg.scripts[destKey] = current != null ? mergeScriptValue(v, current) : v
481
+ const staleKey = `grunt:yolo:${k}`
482
+ if (destPkg.scripts[staleKey] === v) delete destPkg.scripts[staleKey]
483
+ }
484
+ rewriteNpmRunRefs(destPkg.scripts, srcKeys)
383
485
  destPkg.devDependencies = { ...(destPkg.devDependencies || {}) }
384
486
  destPkg.devDependencies["smol-toml"] = srcPkg.devDependencies["smol-toml"]
385
487
  destPkg.devDependencies["rulesync"] = srcPkg.devDependencies["rulesync"]
@@ -406,6 +508,17 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
406
508
  const self = samePath(dest, pkgRoot)
407
509
 
408
510
  phase("merge", () => {
511
+ const wsSkills = path.join(dest, WORKSPACE_SKILLS_REL)
512
+ const pkgSkills = path.join(pkgRoot, WORKSPACE_SKILLS_REL)
513
+ if (!samePath(wsSkills, pkgSkills)) {
514
+ for (const c of findSkillContentConflicts({
515
+ workspaceSkillsDir: wsSkills,
516
+ packagedSkillsDir: pkgSkills,
517
+ })) {
518
+ console.warn(formatSkillConflictWarn(c.name))
519
+ }
520
+ }
521
+
409
522
  for (const dir of COPY_DIRS) {
410
523
  const src = path.join(pkgRoot, dir)
411
524
  const d = path.join(dest, dir)
@@ -454,12 +567,12 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
454
567
  }
455
568
  runNpm("install", ["install"])
456
569
  const guardedSnap = snapshotGuardedRoots(dest)
457
- runNpm("generate", ["run", "rulesync:generate"])
570
+ runNpm("generate", ["run", toGruntScriptName("rulesync:generate")])
458
571
  remergeGuardedRoots(dest, guardedSnap)
459
572
  if (!skipGlobalsApply) {
460
- runNpm("sync-globals", ["run", "sync:globals:apply"])
573
+ runNpm("sync-globals", ["run", toGruntScriptName("sync:globals:apply")])
461
574
  }
462
575
  withGuardedCheckInteriors(dest, () => {
463
- runNpm("check", ["run", "rulesync:check"])
576
+ runNpm("check", ["run", toGruntScriptName("rulesync:check")])
464
577
  })
465
578
  }