@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.
- package/.agents/agents/grunt.md +2 -1
- package/.agents/agents/implementer.md +1 -1
- package/.agents/agents/orchestrator.md +17 -12
- package/.agents/agents/thinker.md +6 -5
- package/.agents/skills/ask/SKILL.md +18 -0
- package/.agents/skills/auto/SKILL.md +18 -0
- package/.agents/skills/browser/SKILL.md +4 -3
- package/.agents/skills/cascade/SKILL.md +5 -3
- package/.agents/skills/handoff/SKILL.md +1 -1
- package/.agents/skills/implement-plan/SKILL.md +12 -11
- package/.agents/skills/solo/SKILL.md +5 -3
- package/.agents/skills/tmp/SKILL.md +58 -0
- package/.agents/skills/write-plan/SKILL.md +11 -8
- package/.claude/agents/grunt.md +2 -1
- package/.claude/agents/implementer.md +1 -1
- package/.claude/agents/orchestrator.md +17 -12
- package/.claude/agents/thinker.md +6 -5
- package/.claude/skills/ask/SKILL.md +18 -0
- package/.claude/skills/auto/SKILL.md +18 -0
- package/.claude/skills/browser/SKILL.md +4 -3
- package/.claude/skills/cascade/SKILL.md +5 -3
- package/.claude/skills/handoff/SKILL.md +1 -1
- package/.claude/skills/implement-plan/SKILL.md +12 -11
- package/.claude/skills/solo/SKILL.md +5 -3
- package/.claude/skills/tmp/SKILL.md +58 -0
- package/.claude/skills/write-plan/SKILL.md +11 -8
- package/.codex/agents/grunt.toml +2 -1
- package/.codex/agents/implementer.toml +1 -1
- package/.codex/agents/orchestrator.toml +17 -12
- package/.codex/agents/thinker.toml +6 -5
- package/.grok/agents/grunt.md +2 -1
- package/.grok/agents/implementer.md +1 -1
- package/.grok/agents/orchestrator.md +17 -12
- package/.grok/agents/thinker.md +6 -5
- package/.grok/hooks/orchestrate-parent.js +280 -62
- package/.grok/skills/ask/SKILL.md +18 -0
- package/.grok/skills/auto/SKILL.md +18 -0
- package/.grok/skills/browser/SKILL.md +4 -3
- package/.grok/skills/cascade/SKILL.md +5 -3
- package/.grok/skills/handoff/SKILL.md +1 -1
- package/.grok/skills/implement-plan/SKILL.md +12 -11
- package/.grok/skills/solo/SKILL.md +5 -3
- package/.grok/skills/tmp/SKILL.md +58 -0
- package/.grok/skills/write-plan/SKILL.md +11 -8
- package/.rulesync/grunt.config.jsonc +7 -0
- package/.rulesync/reference/INDEX.md +7 -4
- package/.rulesync/reference/cascade.md +15 -13
- package/.rulesync/reference/hooks.md +6 -4
- package/.rulesync/reference/law.md +1 -1
- package/.rulesync/reference/map.md +14 -9
- package/.rulesync/reference/output.md +18 -8
- package/.rulesync/reference/plan-format.md +10 -10
- package/.rulesync/reference/skills-map.md +6 -3
- package/.rulesync/rules/CLAUDE.md +18 -13
- package/.rulesync/rules/overview.md +18 -13
- package/.rulesync/skills/ask/SKILL.md +18 -0
- package/.rulesync/skills/auto/SKILL.md +18 -0
- package/.rulesync/skills/browser/SKILL.md +2 -2
- package/.rulesync/skills/cascade/SKILL.md +5 -3
- package/.rulesync/skills/handoff/SKILL.md +1 -1
- package/.rulesync/skills/implement-plan/SKILL.md +14 -11
- package/.rulesync/skills/solo/SKILL.md +5 -3
- package/.rulesync/skills/tmp/SKILL.md +58 -0
- package/.rulesync/skills/write-plan/SKILL.md +12 -8
- package/.rulesync/subagents/grunt.md +2 -1
- package/.rulesync/subagents/implementer.md +1 -1
- package/.rulesync/subagents/orchestrator.md +17 -12
- package/.rulesync/subagents/thinker.md +6 -5
- package/AGENTS.md +18 -13
- package/CHANGELOG.md +33 -0
- package/CLAUDE.md +18 -13
- package/README.md +17 -16
- package/cli/grunt.mjs +12 -12
- package/cli/grunt.test.ts +29 -20
- package/cli/init.mjs +116 -22
- package/cli/init.test.ts +545 -34
- package/package.json +8 -1
- package/scripts/gate-fat-tools.mjs +2 -2
- package/scripts/grunt-config.mjs +50 -0
- package/scripts/persist-plan.mjs +17 -2
- package/scripts/persist-tmp.mjs +123 -0
- 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.
|
|
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
|
+
}
|
package/scripts/persist-plan.mjs
CHANGED
|
@@ -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
|
|