@lovrozagar/grunt 0.5.0 → 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 (82) hide show
  1. package/.agents/agents/grunt.md +2 -1
  2. package/.agents/agents/implementer.md +1 -1
  3. package/.agents/agents/orchestrator.md +17 -12
  4. package/.agents/agents/thinker.md +6 -5
  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/handoff/SKILL.md +1 -1
  10. package/.agents/skills/implement-plan/SKILL.md +12 -11
  11. package/.agents/skills/solo/SKILL.md +5 -3
  12. package/.agents/skills/tmp/SKILL.md +58 -0
  13. package/.agents/skills/write-plan/SKILL.md +11 -8
  14. package/.claude/agents/grunt.md +2 -1
  15. package/.claude/agents/implementer.md +1 -1
  16. package/.claude/agents/orchestrator.md +17 -12
  17. package/.claude/agents/thinker.md +6 -5
  18. package/.claude/skills/ask/SKILL.md +18 -0
  19. package/.claude/skills/auto/SKILL.md +18 -0
  20. package/.claude/skills/browser/SKILL.md +4 -3
  21. package/.claude/skills/cascade/SKILL.md +5 -3
  22. package/.claude/skills/handoff/SKILL.md +1 -1
  23. package/.claude/skills/implement-plan/SKILL.md +12 -11
  24. package/.claude/skills/solo/SKILL.md +5 -3
  25. package/.claude/skills/tmp/SKILL.md +58 -0
  26. package/.claude/skills/write-plan/SKILL.md +11 -8
  27. package/.codex/agents/grunt.toml +2 -1
  28. package/.codex/agents/implementer.toml +1 -1
  29. package/.codex/agents/orchestrator.toml +17 -12
  30. package/.codex/agents/thinker.toml +6 -5
  31. package/.grok/agents/grunt.md +2 -1
  32. package/.grok/agents/implementer.md +1 -1
  33. package/.grok/agents/orchestrator.md +17 -12
  34. package/.grok/agents/thinker.md +6 -5
  35. package/.grok/hooks/orchestrate-parent.js +280 -62
  36. package/.grok/skills/ask/SKILL.md +18 -0
  37. package/.grok/skills/auto/SKILL.md +18 -0
  38. package/.grok/skills/browser/SKILL.md +4 -3
  39. package/.grok/skills/cascade/SKILL.md +5 -3
  40. package/.grok/skills/handoff/SKILL.md +1 -1
  41. package/.grok/skills/implement-plan/SKILL.md +12 -11
  42. package/.grok/skills/solo/SKILL.md +5 -3
  43. package/.grok/skills/tmp/SKILL.md +58 -0
  44. package/.grok/skills/write-plan/SKILL.md +11 -8
  45. package/.rulesync/grunt.config.jsonc +7 -0
  46. package/.rulesync/reference/INDEX.md +7 -4
  47. package/.rulesync/reference/cascade.md +15 -13
  48. package/.rulesync/reference/hooks.md +6 -4
  49. package/.rulesync/reference/law.md +1 -1
  50. package/.rulesync/reference/map.md +14 -9
  51. package/.rulesync/reference/output.md +18 -8
  52. package/.rulesync/reference/plan-format.md +10 -10
  53. package/.rulesync/reference/skills-map.md +6 -3
  54. package/.rulesync/rules/CLAUDE.md +18 -13
  55. package/.rulesync/rules/overview.md +18 -13
  56. package/.rulesync/skills/ask/SKILL.md +18 -0
  57. package/.rulesync/skills/auto/SKILL.md +18 -0
  58. package/.rulesync/skills/browser/SKILL.md +2 -2
  59. package/.rulesync/skills/cascade/SKILL.md +5 -3
  60. package/.rulesync/skills/handoff/SKILL.md +1 -1
  61. package/.rulesync/skills/implement-plan/SKILL.md +14 -11
  62. package/.rulesync/skills/solo/SKILL.md +5 -3
  63. package/.rulesync/skills/tmp/SKILL.md +58 -0
  64. package/.rulesync/skills/write-plan/SKILL.md +12 -8
  65. package/.rulesync/subagents/grunt.md +2 -1
  66. package/.rulesync/subagents/implementer.md +1 -1
  67. package/.rulesync/subagents/orchestrator.md +17 -12
  68. package/.rulesync/subagents/thinker.md +6 -5
  69. package/AGENTS.md +18 -13
  70. package/CHANGELOG.md +33 -0
  71. package/CLAUDE.md +18 -13
  72. package/README.md +17 -16
  73. package/cli/grunt.mjs +12 -12
  74. package/cli/grunt.test.ts +29 -20
  75. package/cli/init.mjs +116 -22
  76. package/cli/init.test.ts +545 -34
  77. package/package.json +8 -1
  78. package/scripts/gate-fat-tools.mjs +2 -2
  79. package/scripts/grunt-config.mjs +50 -0
  80. package/scripts/persist-plan.mjs +17 -2
  81. package/scripts/persist-tmp.mjs +123 -0
  82. package/scripts/scrub-spawn-prompt.mjs +1 -1
package/cli/init.mjs CHANGED
@@ -21,8 +21,10 @@ const PRODUCT_SCRIPTS = [
21
21
  "hooks-union.mjs",
22
22
  "pipeline.mjs",
23
23
  "grunt-job.mjs",
24
+ "grunt-config.mjs",
24
25
  "parse-need.mjs",
25
26
  "persist-handoff.mjs",
27
+ "persist-tmp.mjs",
26
28
  "persist-plan.mjs",
27
29
  "purge-global-mcps.mjs",
28
30
  "scrub-spawn-prompt.mjs",
@@ -38,14 +40,30 @@ const COPY_DIRS = [".rulesync", ".grok", ".codex", ".claude", ".agents"]
38
40
  const GUARDED_MD_FILES = ["AGENTS.md", "CLAUDE.md"]
39
41
  export const GUARDED_ROOT_FILES = ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]
40
42
  // .mcp.json is intentionally not copied here: scripts/emit-mcp-policy.mjs
41
- // (run via `rulesync:generate`, see mergeClaudeSettings ordering) owns and
43
+ // (run via consumer `grunt:rulesync:generate`, see mergeClaudeSettings ordering) owns and
42
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
+ }
43
56
  const OWNED_HOOK_FILES = ["scrub-spawn-prompt.mjs", "gate-fat-tools.mjs", "orchestrate-parent.js"]
44
57
  export const SENTINEL_BEGIN = "<!-- grunt:begin -->"
45
58
  export const SENTINEL_END = "<!-- grunt:end -->"
46
59
  export const MAX_GUARDED_MARKDOWN_BYTES = 2 * 1024 * 1024
47
- const GRUNT_REGION_RE = /<!-- grunt:begin -->\r?\n?[\s\S]*?<!-- grunt:end -->\r?\n?/g
48
- 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]*$/
49
67
 
50
68
  function sortKeys(obj) {
51
69
  return Object.fromEntries(Object.keys(obj).sort().map((k) => [k, obj[k]]))
@@ -118,18 +136,43 @@ function inspectGuardedBuffer(buf) {
118
136
  }
119
137
 
120
138
  export function extractUserMarkdown(text) {
121
- 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")
122
157
  }
123
158
 
124
159
  export function extractGruntBody(text) {
160
+ const src = String(text)
125
161
  const parts = []
126
162
  const re = cloneRe(GRUNT_INTERIOR_RE)
127
163
  let m
128
- while ((m = re.exec(String(text)))) {
129
- parts.push(trimTrailingNewlines(m[1]))
164
+ while ((m = re.exec(src))) {
165
+ parts.push(trimTrailingNewlines(unwrapStackedMarkers(m[1])))
166
+ }
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
+ )
130
173
  }
131
- if (!parts.length) return null
132
- return parts.join("\n")
174
+ if (!src.includes(SENTINEL_END)) return null
175
+ return trimTrailingNewlines(unwrapStackedMarkers(src))
133
176
  }
134
177
 
135
178
  function userRemainderIsBlank(user) {
@@ -137,9 +180,10 @@ function userRemainderIsBlank(user) {
137
180
  }
138
181
 
139
182
  export function composeGuardedMarkdown(gruntBody, userRemainder, nl = "\n") {
140
- const body = toNewline(trimTrailingNewlines(gruntBody), nl)
183
+ const raw = extractGruntBody(gruntBody) ?? gruntBody
184
+ const body = toNewline(trimTrailingNewlines(raw), nl)
141
185
  const block = `${SENTINEL_BEGIN}${nl}${body}${nl}${SENTINEL_END}${nl}`
142
- const user = userRemainder == null ? "" : String(userRemainder)
186
+ const user = extractUserMarkdown(userRemainder == null ? "" : String(userRemainder))
143
187
  if (!user || userRemainderIsBlank(user)) return block
144
188
  return block + toNewline(user, nl)
145
189
  }
@@ -184,7 +228,7 @@ export function mergeGuardedMarkdown(dest, pkgRoot, file, { alreadyInited = fals
184
228
  const d = path.join(dest, file)
185
229
  if (samePath(src, d)) return
186
230
  const srcText = fs.readFileSync(src, "utf8")
187
- return writeMergedGuardedFile(d, srcText)
231
+ return writeMergedGuardedFile(d, extractGruntBody(srcText) ?? srcText)
188
232
  }
189
233
 
190
234
  export function snapshotGuardedRoots(root) {
@@ -339,7 +383,13 @@ export function mergeClaudeSettings(destRoot, pkgRoot) {
339
383
  }
340
384
 
341
385
  function looksGruntOwnedPrefix(prefix) {
342
- 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, "\\$&")
343
393
  }
344
394
 
345
395
  function commandCount(script) {
@@ -364,14 +414,29 @@ function extraOwnedSuffix(cur, newSrc) {
364
414
  return null
365
415
  }
366
416
 
367
- function mergeScriptValue(name, newSrc, cur) {
368
- if (cur == null) return newSrc
417
+ function mergeScriptValue(newSrc, cur) {
369
418
  if (cur.startsWith(newSrc)) return cur
370
419
  const suffix = extraOwnedSuffix(cur, newSrc)
371
420
  if (suffix != null) return newSrc + suffix
372
- if (looksGruntOwnedPrefix(cur)) return newSrc
373
- console.warn(`script \`${name}\` left untouched (unrelated customization)`)
374
- 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
+ }
375
440
  }
376
441
 
377
442
  export function mergePackageJson(dest, pkgRoot) {
@@ -384,10 +449,39 @@ export function mergePackageJson(dest, pkgRoot) {
384
449
 
385
450
  const srcPkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"))
386
451
  destPkg.scripts = { ...(destPkg.scripts || {}) }
452
+ const srcKeys = []
387
453
  for (const [k, v] of Object.entries(srcPkg.scripts || {})) {
388
- if (k === "test" || k.endsWith(":raw")) continue
389
- 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
+ }
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]
390
483
  }
484
+ rewriteNpmRunRefs(destPkg.scripts, srcKeys)
391
485
  destPkg.devDependencies = { ...(destPkg.devDependencies || {}) }
392
486
  destPkg.devDependencies["smol-toml"] = srcPkg.devDependencies["smol-toml"]
393
487
  destPkg.devDependencies["rulesync"] = srcPkg.devDependencies["rulesync"]
@@ -473,12 +567,12 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
473
567
  }
474
568
  runNpm("install", ["install"])
475
569
  const guardedSnap = snapshotGuardedRoots(dest)
476
- runNpm("generate", ["run", "rulesync:generate"])
570
+ runNpm("generate", ["run", toGruntScriptName("rulesync:generate")])
477
571
  remergeGuardedRoots(dest, guardedSnap)
478
572
  if (!skipGlobalsApply) {
479
- runNpm("sync-globals", ["run", "sync:globals:apply"])
573
+ runNpm("sync-globals", ["run", toGruntScriptName("sync:globals:apply")])
480
574
  }
481
575
  withGuardedCheckInteriors(dest, () => {
482
- runNpm("check", ["run", "rulesync:check"])
576
+ runNpm("check", ["run", toGruntScriptName("rulesync:check")])
483
577
  })
484
578
  }