@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovrozagar/grunt",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",
@@ -34,8 +34,10 @@
34
34
  "scripts/hooks-union.mjs",
35
35
  "scripts/pipeline.mjs",
36
36
  "scripts/grunt-job.mjs",
37
+ "scripts/grunt-config.mjs",
37
38
  "scripts/parse-need.mjs",
38
39
  "scripts/persist-handoff.mjs",
40
+ "scripts/persist-tmp.mjs",
39
41
  "scripts/persist-plan.mjs",
40
42
  "scripts/purge-global-mcps.mjs",
41
43
  "scripts/scrub-spawn-prompt.mjs",
@@ -77,6 +79,11 @@
77
79
  "sync:globals:apply": "node scripts/sync-global-settings.mjs --apply",
78
80
  "purge:global-mcps": "node scripts/purge-global-mcps.mjs",
79
81
  "purge:global-mcps:apply": "node scripts/purge-global-mcps.mjs --apply",
82
+ "antigravity": "antigravity --dangerously-skip-permissions",
83
+ "claude": "claude --dangerously-skip-permissions",
84
+ "codex": "codex --dangerously-bypass-approvals-and-sandbox",
85
+ "gemini": "gemini --yolo",
86
+ "grok": "grok --yolo",
80
87
  "test": "vitest run --coverage"
81
88
  },
82
89
  "devDependencies": {
@@ -439,7 +439,7 @@ function resolveWriteAbs(filePath, workspaceRoot) {
439
439
 
440
440
  function isUnderPlansDir(abs, workspaceRoot) {
441
441
  if (!abs || !workspaceRoot) return false;
442
- const dir = path.resolve(workspaceRoot, ".tmp", "plans");
442
+ const dir = path.resolve(workspaceRoot, ".tmp", "grunt", "plans");
443
443
  if (abs === dir) return true;
444
444
  const rel = path.relative(dir, abs);
445
445
  return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
@@ -487,7 +487,7 @@ function inProgressStatus(text) {
487
487
  }
488
488
 
489
489
  function loadInProgressPlans(workspaceRoot) {
490
- const dir = path.resolve(workspaceRoot, ".tmp", "plans");
490
+ const dir = path.resolve(workspaceRoot, ".tmp", "grunt", "plans");
491
491
  let names;
492
492
  try {
493
493
  names = fs.readdirSync(dir);
@@ -0,0 +1,50 @@
1
+ /** Load leftoverGate + spawnMode from `.rulesync/grunt.config.jsonc`. leftover fail-closed `"ask"`; spawn fail-closed `"cascade"`. Keys independent. */
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ export const CONFIG_REL = ".rulesync/grunt.config.jsonc";
6
+ export const LEFTOVER_GATES = new Set(["auto", "ask"]);
7
+ export const SPAWN_MODES = new Set(["solo", "cascade"]);
8
+
9
+ export function stripJsonc(text) {
10
+ return String(text ?? "")
11
+ .replace(/\/\*[\s\S]*?\*\//g, "")
12
+ .replace(/\/\/.*$/gm, "");
13
+ }
14
+
15
+ function readConfigObject(workspaceRoot) {
16
+ if (!workspaceRoot) return null;
17
+ const raw = fs.readFileSync(path.join(workspaceRoot, CONFIG_REL), "utf8");
18
+ const obj = JSON.parse(stripJsonc(raw));
19
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) return null;
20
+ if (obj.version !== 1) return null;
21
+ return obj;
22
+ }
23
+
24
+ /** @returns {"auto"|"ask"} */
25
+ export function loadLeftoverGate(workspaceRoot) {
26
+ try {
27
+ const obj = readConfigObject(workspaceRoot);
28
+ if (!obj) return "ask";
29
+ if (obj.leftoverGate === "auto" || obj.leftoverGate === "ask") {
30
+ return obj.leftoverGate;
31
+ }
32
+ return "ask";
33
+ } catch {
34
+ return "ask";
35
+ }
36
+ }
37
+
38
+ /** @returns {"solo"|"cascade"} */
39
+ export function loadSpawnMode(workspaceRoot) {
40
+ try {
41
+ const obj = readConfigObject(workspaceRoot);
42
+ if (!obj) return "cascade";
43
+ if (obj.spawnMode === "solo" || obj.spawnMode === "cascade") {
44
+ return obj.spawnMode;
45
+ }
46
+ return "cascade";
47
+ } catch {
48
+ return "cascade";
49
+ }
50
+ }
@@ -1,10 +1,24 @@
1
1
  #!/usr/bin/env node
2
- /** Allocate/slugify/validate/write a local plan under .tmp/plans/. */
2
+ /** Allocate/slugify/validate/write a local plan under .tmp/grunt/plans/. */
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
 
7
- export const PLAN_DIR = ".tmp/plans";
7
+ export const PLAN_DIR = ".tmp/grunt/plans";
8
+ /** One-shot rename source; drop next release. */
9
+ export const LEGACY_PLAN_DIR = ".tmp/plans";
10
+
11
+ /** If `.tmp/plans` exists and `.tmp/grunt/plans` is missing, rename. Both exist: leave old. */
12
+ export function migrateLegacyPlansDir(workspaceRoot) {
13
+ const ws = workspaceRoot || process.cwd();
14
+ const legacy = path.join(ws, LEGACY_PLAN_DIR);
15
+ const next = path.join(ws, PLAN_DIR);
16
+ if (fs.existsSync(legacy) && !fs.existsSync(next)) {
17
+ fs.mkdirSync(path.dirname(next), { recursive: true });
18
+ fs.renameSync(legacy, next);
19
+ }
20
+ return next;
21
+ }
8
22
  /** Old unstamped names and new `{serial}-{slug}-{YYYYMMDDTHHMMSSZ}.md`. */
9
23
  export const FILENAME_RE =
10
24
  /^[0-9]+-[a-z0-9]+(-[a-z0-9]+)*(-\d{8}T\d{6}Z)?\.md$/;
@@ -237,6 +251,7 @@ export function persistPlan({
237
251
  source ||
238
252
  (raw.match(/^\s*source:\s*(.+)$/m) ? undefined : planName);
239
253
  const body = stripPlanName(raw);
254
+ migrateLegacyPlansDir(ws);
240
255
  const plansDir = path.join(ws, PLAN_DIR);
241
256
  fs.mkdirSync(plansDir, { recursive: true });
242
257
  const createdAt =
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ /** Allocate/slugify/write a one-off convo artifact under .tmp/grunt/. */
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import {
7
+ DATETIME_RE,
8
+ compactCreatedStamp,
9
+ ensureGitignore,
10
+ slugify,
11
+ utcDateTime,
12
+ } from "./persist-plan.mjs";
13
+
14
+ export const TMP_DIR = ".tmp/grunt";
15
+ /** `{serial}-{slug}-{YYYYMMDDTHHMMSSZ}.{ext}`; stamp required. */
16
+ export const FILENAME_RE =
17
+ /^[1-9][0-9]*-[a-z0-9]+(-[a-z0-9]+)*-\d{8}T\d{6}Z\.[a-z0-9]+$/;
18
+ export const FILENAME_STAMP_RE = /-\d{8}T\d{6}Z$/;
19
+ const META_LINE_RE = /^\s*TMP_(NAME|EXT):\s*.+\s*$/;
20
+ const NAME_RE = /^\s*TMP_NAME:\s*(.+?)\s*$/m;
21
+ const EXT_RE = /^\s*TMP_EXT:\s*(.+?)\s*$/m;
22
+
23
+ export function extractTmpName(content) {
24
+ const m = String(content).match(NAME_RE);
25
+ return m ? m[1].trim() : "";
26
+ }
27
+
28
+ export function extractTmpExt(content) {
29
+ const m = String(content).match(EXT_RE);
30
+ return m ? m[1].trim() : "";
31
+ }
32
+
33
+ export function stripTmpMeta(content) {
34
+ return String(content)
35
+ .replace(/^\uFEFF/, "")
36
+ .split(/\r?\n/)
37
+ .filter((line) => !META_LINE_RE.test(line))
38
+ .join("\n");
39
+ }
40
+
41
+ export function normalizeTmpExt(raw) {
42
+ let e = String(raw || "")
43
+ .trim()
44
+ .toLowerCase();
45
+ if (e.startsWith(".")) e = e.slice(1);
46
+ if (!/^[a-z0-9]+$/.test(e) || e.length > 16) return "";
47
+ return e;
48
+ }
49
+
50
+ function nextTmpSerial(dir) {
51
+ let next = 1;
52
+ if (!fs.existsSync(dir)) return next;
53
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
54
+ if (!ent.isFile()) continue;
55
+ const m = ent.name.match(/^([0-9]+)-/);
56
+ if (!m) continue;
57
+ const n = parseInt(m[1], 10);
58
+ if (Number.isFinite(n) && n >= next) next = n + 1;
59
+ }
60
+ return next;
61
+ }
62
+
63
+ export function persistTmp({ workspaceRoot, content, created } = {}) {
64
+ const ws = workspaceRoot || process.cwd();
65
+ const raw = String(content || "");
66
+ if (raw.includes("\0")) return { ok: false, error: "binary" };
67
+ const name = extractTmpName(raw);
68
+ if (!name) return { ok: false, error: "missing TMP_NAME" };
69
+ const extRaw = extractTmpExt(raw);
70
+ const ext = extRaw ? normalizeTmpExt(extRaw) : "md";
71
+ if (!ext) return { ok: false, error: "ext" };
72
+ const slug = slugify(name);
73
+ const body = stripTmpMeta(raw);
74
+ const dir = path.join(ws, TMP_DIR);
75
+ fs.mkdirSync(dir, { recursive: true });
76
+ const createdAt = created && DATETIME_RE.test(created) ? created : utcDateTime();
77
+ const stamp = compactCreatedStamp(createdAt);
78
+ let serial = nextTmpSerial(dir);
79
+ let filename = `${serial}-${slug}-${stamp}.${ext}`;
80
+ let dest = path.join(dir, filename);
81
+ let tries = 0;
82
+ while (fs.existsSync(dest) && tries < 5) {
83
+ serial += 1;
84
+ filename = `${serial}-${slug}-${stamp}.${ext}`;
85
+ dest = path.join(dir, filename);
86
+ tries += 1;
87
+ }
88
+ if (fs.existsSync(dest)) return { ok: false, error: "serial collision" };
89
+ if (!FILENAME_RE.test(filename)) return { ok: false, error: "filename" };
90
+ ensureGitignore(ws);
91
+ fs.writeFileSync(dest, body);
92
+ return { ok: true, path: dest, serial, slug, ext, content: body, filename };
93
+ }
94
+
95
+ function main() {
96
+ try {
97
+ const wsIdx = process.argv.indexOf("--workspace");
98
+ const workspaceRoot = wsIdx >= 0 ? process.argv[wsIdx + 1] : process.cwd();
99
+ const content = fs.readFileSync(0, "utf8");
100
+ const result = persistTmp({ workspaceRoot, content });
101
+ if (!result.ok) {
102
+ process.stderr.write((result.error || "invalid tmp") + "\n");
103
+ return 1;
104
+ }
105
+ process.stdout.write(
106
+ JSON.stringify({
107
+ serial: result.serial,
108
+ path: result.path,
109
+ filename: result.filename,
110
+ }) + "\n",
111
+ );
112
+ return 0;
113
+ } catch (err) {
114
+ process.stderr.write(String(err && err.message ? err.message : err) + "\n");
115
+ return 1;
116
+ }
117
+ }
118
+
119
+ const thisFile = fileURLToPath(import.meta.url);
120
+ const invoked = process.argv[1] ? path.resolve(process.argv[1]) : "";
121
+ if (invoked === thisFile || import.meta.url === pathToFileURL(invoked).href) {
122
+ process.exit(main());
123
+ }
@@ -90,7 +90,7 @@ export function workspaceRootOf(data) {
90
90
  export function spawnCapReason(workspaceRoot) {
91
91
  const root = workspaceRoot || process.cwd() || "";
92
92
  return (
93
- `spawn prompt exceeds ${MAX_PROMPT_CHARS} chars after scrub; write it under ${root}/.tmp/plans/ and re-spawn with that abs path`
93
+ `spawn prompt exceeds ${MAX_PROMPT_CHARS} chars after scrub; write it under ${root}/.tmp/grunt/plans/ and re-spawn with that abs path`
94
94
  );
95
95
  }
96
96