@lovrozagar/grunt 0.5.0 → 0.6.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.
- package/.agents/agents/grunt.md +2 -23
- package/.agents/agents/orchestrator.md +5 -45
- package/.agents/skills/ask/SKILL.md +11 -0
- package/.agents/skills/auto/SKILL.md +13 -0
- package/.agents/skills/browser/SKILL.md +4 -7
- package/.agents/skills/clasp/SKILL.md +11 -0
- package/.agents/skills/commit/SKILL.md +3 -1
- package/.agents/skills/commit-push/SKILL.md +2 -0
- package/.agents/skills/commit-push-deploy/SKILL.md +2 -0
- package/.agents/skills/commit-push-release/SKILL.md +2 -0
- package/.agents/skills/explain/SKILL.md +7 -13
- package/.agents/skills/google-workspace/SKILL.md +13 -0
- package/.agents/skills/handoff/SKILL.md +7 -11
- package/.agents/skills/implement-plan/SKILL.md +5 -108
- package/.agents/skills/listen/SKILL.md +15 -0
- package/.agents/skills/pickup/SKILL.md +6 -12
- package/.agents/skills/speak/SKILL.md +13 -0
- package/.agents/skills/tmp/SKILL.md +54 -0
- package/.agents/skills/write-plan/SKILL.md +5 -74
- package/.claude/agents/grunt.md +2 -25
- package/.claude/agents/orchestrator.md +12 -45
- package/.claude/skills/ask/SKILL.md +11 -0
- package/.claude/skills/auto/SKILL.md +13 -0
- package/.claude/skills/browser/SKILL.md +4 -7
- package/.claude/skills/clasp/SKILL.md +11 -0
- package/.claude/skills/commit/SKILL.md +3 -1
- package/.claude/skills/commit-push/SKILL.md +2 -0
- package/.claude/skills/commit-push-deploy/SKILL.md +2 -0
- package/.claude/skills/commit-push-release/SKILL.md +2 -0
- package/.claude/skills/explain/SKILL.md +7 -13
- package/.claude/skills/google-workspace/SKILL.md +13 -0
- package/.claude/skills/handoff/SKILL.md +7 -11
- package/.claude/skills/implement-plan/SKILL.md +5 -108
- package/.claude/skills/listen/SKILL.md +15 -0
- package/.claude/skills/pickup/SKILL.md +6 -12
- package/.claude/skills/speak/SKILL.md +13 -0
- package/.claude/skills/tmp/SKILL.md +54 -0
- package/.claude/skills/write-plan/SKILL.md +5 -74
- package/.codex/agents/grunt.toml +2 -25
- package/.codex/agents/orchestrator.toml +4 -45
- package/.grok/agents/grunt.md +2 -23
- package/.grok/agents/orchestrator.md +4 -43
- package/.grok/global-settings.toml +1 -3
- package/.grok/hooks/orchestrate-parent.js +278 -228
- package/.grok/skills/ask/SKILL.md +11 -0
- package/.grok/skills/auto/SKILL.md +13 -0
- package/.grok/skills/browser/SKILL.md +4 -7
- package/.grok/skills/clasp/SKILL.md +11 -0
- package/.grok/skills/commit/SKILL.md +3 -1
- package/.grok/skills/commit-push/SKILL.md +2 -0
- package/.grok/skills/commit-push-deploy/SKILL.md +2 -0
- package/.grok/skills/commit-push-release/SKILL.md +2 -0
- package/.grok/skills/explain/SKILL.md +7 -13
- package/.grok/skills/google-workspace/SKILL.md +13 -0
- package/.grok/skills/handoff/SKILL.md +7 -11
- package/.grok/skills/implement-plan/SKILL.md +5 -108
- package/.grok/skills/listen/SKILL.md +15 -0
- package/.grok/skills/pickup/SKILL.md +6 -12
- package/.grok/skills/speak/SKILL.md +13 -0
- package/.grok/skills/tmp/SKILL.md +54 -0
- package/.grok/skills/write-plan/SKILL.md +5 -74
- package/.rulesync/hooks.jsonc +1 -1
- package/.rulesync/mcp-policy.jsonc +0 -2
- package/.rulesync/reference/INDEX.md +30 -17
- package/.rulesync/reference/browser.md +13 -6
- package/.rulesync/reference/cascade.md +6 -84
- package/.rulesync/reference/clasp.md +21 -0
- package/.rulesync/reference/google-workspace.md +79 -0
- package/.rulesync/reference/hooks.md +7 -9
- package/.rulesync/reference/implementation-format.md +113 -0
- package/.rulesync/reference/law.md +5 -3
- package/.rulesync/reference/listen.md +80 -0
- package/.rulesync/reference/map.md +39 -31
- package/.rulesync/reference/output.md +3 -41
- package/.rulesync/reference/plan-format.md +14 -12
- package/.rulesync/reference/refs-map.md +12 -5
- package/.rulesync/reference/rtk.md +3 -2
- package/.rulesync/reference/scope.md +37 -0
- package/.rulesync/reference/setup.md +27 -0
- package/.rulesync/reference/skills-map.md +13 -9
- package/.rulesync/reference/speak.md +59 -0
- package/.rulesync/rules/CLAUDE.md +3 -44
- package/.rulesync/rules/overview.md +9 -42
- package/.rulesync/skills/ask/SKILL.md +11 -0
- package/.rulesync/skills/auto/SKILL.md +11 -0
- package/.rulesync/skills/browser/SKILL.md +4 -8
- package/.rulesync/skills/clasp/SKILL.md +9 -0
- package/.rulesync/skills/commit/SKILL.md +3 -1
- package/.rulesync/skills/commit-push/SKILL.md +2 -0
- package/.rulesync/skills/commit-push-deploy/SKILL.md +2 -0
- package/.rulesync/skills/commit-push-release/SKILL.md +2 -0
- package/.rulesync/skills/explain/SKILL.md +7 -14
- package/.rulesync/skills/google-workspace/SKILL.md +11 -0
- package/.rulesync/skills/handoff/SKILL.md +7 -11
- package/.rulesync/skills/implement-plan/SKILL.md +5 -116
- package/.rulesync/skills/listen/SKILL.md +13 -0
- package/.rulesync/skills/pickup/SKILL.md +6 -12
- package/.rulesync/skills/speak/SKILL.md +13 -0
- package/.rulesync/skills/tmp/SKILL.md +54 -0
- package/.rulesync/skills/write-plan/SKILL.md +5 -80
- package/.rulesync/subagents/grunt.md +2 -23
- package/.rulesync/subagents/orchestrator.md +14 -54
- package/AGENTS.md +9 -42
- package/CHANGELOG.md +89 -0
- package/CLAUDE.md +1 -44
- package/README.md +97 -88
- package/cli/grunt.mjs +29 -13
- package/cli/grunt.test.ts +62 -20
- package/cli/init.mjs +218 -31
- package/cli/init.test.ts +669 -46
- package/cli/prompt.mjs +8 -0
- package/cli/prompt.test.ts +14 -0
- package/package.json +13 -1
- package/scripts/browser.mjs +98 -5
- package/scripts/doctor.mjs +82 -1
- package/scripts/emit-gemini.mjs +1 -1
- package/scripts/emit-mcp-policy.mjs +1 -1
- package/scripts/gate-fat-tools.mjs +223 -173
- package/scripts/google-workspace.mjs +713 -0
- package/scripts/grunt-job.mjs +453 -69
- package/scripts/listen.mjs +829 -0
- package/scripts/parse-need.mjs +21 -2
- package/scripts/persist-implementation.mjs +278 -0
- package/scripts/persist-plan.mjs +17 -2
- package/scripts/persist-tmp.mjs +123 -0
- package/scripts/scrub-spawn-prompt.mjs +4 -51
- package/scripts/setup.mjs +746 -0
- package/scripts/speak.mjs +291 -0
- package/.agents/agents/implementer.md +0 -32
- package/.agents/agents/thinker.md +0 -28
- package/.agents/skills/cascade/SKILL.md +0 -26
- package/.agents/skills/parent/SKILL.md +0 -6
- package/.agents/skills/solo/SKILL.md +0 -23
- package/.claude/agents/implementer.md +0 -37
- package/.claude/agents/thinker.md +0 -35
- package/.claude/skills/cascade/SKILL.md +0 -26
- package/.claude/skills/parent/SKILL.md +0 -6
- package/.claude/skills/solo/SKILL.md +0 -23
- package/.codex/agents/implementer.toml +0 -26
- package/.codex/agents/thinker.toml +0 -25
- package/.grok/agents/implementer.md +0 -27
- package/.grok/agents/thinker.md +0 -26
- package/.grok/parent.md +0 -1
- package/.grok/roles/implementer.toml +0 -5
- package/.grok/roles/thinker.toml +0 -5
- package/.grok/skills/cascade/SKILL.md +0 -26
- package/.grok/skills/parent/SKILL.md +0 -6
- package/.grok/skills/solo/SKILL.md +0 -23
- package/.rulesync/skills/cascade/SKILL.md +0 -26
- package/.rulesync/skills/parent/SKILL.md +0 -6
- package/.rulesync/skills/solo/SKILL.md +0 -23
- package/.rulesync/subagents/implementer.md +0 -49
- package/.rulesync/subagents/thinker.md +0 -48
package/cli/grunt.test.ts
CHANGED
|
@@ -6,6 +6,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
6
6
|
const init = vi.hoisted(() => vi.fn());
|
|
7
7
|
const destAlreadyInited = vi.hoisted(() => vi.fn(() => false));
|
|
8
8
|
const shouldAutoSkipGlobals = vi.hoisted(() => vi.fn(() => false));
|
|
9
|
+
const toGruntScriptName = vi.hoisted(
|
|
10
|
+
() => (k) => (String(k).startsWith("grunt:") ? k : `grunt:${k}`),
|
|
11
|
+
);
|
|
9
12
|
const execFileSync = vi.hoisted(() => vi.fn());
|
|
10
13
|
const isInteractive = vi.hoisted(() => vi.fn(() => false));
|
|
11
14
|
const select = vi.hoisted(() => vi.fn());
|
|
@@ -15,7 +18,14 @@ const spinner = vi.hoisted(() =>
|
|
|
15
18
|
);
|
|
16
19
|
const bailIfCancel = vi.hoisted(() => vi.fn((v) => v));
|
|
17
20
|
|
|
18
|
-
vi.mock("./init.mjs", () => ({
|
|
21
|
+
vi.mock("./init.mjs", () => ({
|
|
22
|
+
init,
|
|
23
|
+
destAlreadyInited,
|
|
24
|
+
shouldAutoSkipGlobals,
|
|
25
|
+
toGruntScriptName,
|
|
26
|
+
RESERVED_SKILLS: ["browser", "tmp", "write-plan"],
|
|
27
|
+
GRUNT_NPM_PREFIX: "grunt:",
|
|
28
|
+
}));
|
|
19
29
|
vi.mock("node:child_process", () => ({ execFileSync }));
|
|
20
30
|
vi.mock("./prompt.mjs", () => ({
|
|
21
31
|
isInteractive,
|
|
@@ -32,12 +42,14 @@ const USAGE = `Usage: grunt [command]
|
|
|
32
42
|
Default (no command): TTY menu; else init — full setup
|
|
33
43
|
|
|
34
44
|
Commands:
|
|
35
|
-
init Full setup: merge SoT, npm install, rulesync:generate, sync:globals:apply, rulesync:check
|
|
36
|
-
generate npm run rulesync:generate
|
|
37
|
-
check npm run rulesync:check
|
|
38
|
-
sync-globals npm run sync:globals (dry-run; --apply to write)
|
|
39
|
-
purge-mcps npm run purge:global-mcps (dry-run; --apply to write)
|
|
40
|
-
doctor npm run doctor
|
|
45
|
+
init Full setup: merge SoT, npm install, grunt:rulesync:generate, grunt:sync:globals:apply, grunt:rulesync:check
|
|
46
|
+
generate npm run grunt:rulesync:generate
|
|
47
|
+
check npm run grunt:rulesync:check
|
|
48
|
+
sync-globals npm run grunt:sync:globals (dry-run; --apply to write)
|
|
49
|
+
purge-mcps npm run grunt:purge:global-mcps (dry-run; --apply to write)
|
|
50
|
+
doctor npm run grunt:doctor
|
|
51
|
+
setup npm run grunt:setup — handheld keys/OAuth (speak, listen, google-workspace, browser)
|
|
52
|
+
upgrade Re-init: copy owned files, prune retired grunt-owned names, print reserved skills
|
|
41
53
|
help Show this help
|
|
42
54
|
version Print package version
|
|
43
55
|
|
|
@@ -148,22 +160,22 @@ describe("start", () => {
|
|
|
148
160
|
},
|
|
149
161
|
);
|
|
150
162
|
|
|
151
|
-
it("generate npm-runs rulesync:generate", async () => {
|
|
163
|
+
it("generate npm-runs grunt:rulesync:generate", async () => {
|
|
152
164
|
process.argv = ["node", "grunt", "generate"];
|
|
153
165
|
await start();
|
|
154
166
|
expect(execFileSync).toHaveBeenCalledOnce();
|
|
155
|
-
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "rulesync:generate"], {
|
|
167
|
+
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "grunt:rulesync:generate"], {
|
|
156
168
|
cwd: process.cwd(),
|
|
157
169
|
stdio: "inherit",
|
|
158
170
|
});
|
|
159
171
|
expect(init).not.toHaveBeenCalled();
|
|
160
172
|
});
|
|
161
173
|
|
|
162
|
-
it("check npm-runs rulesync:check", async () => {
|
|
174
|
+
it("check npm-runs grunt:rulesync:check", async () => {
|
|
163
175
|
process.argv = ["node", "grunt", "check"];
|
|
164
176
|
await start();
|
|
165
177
|
expect(execFileSync).toHaveBeenCalledOnce();
|
|
166
|
-
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "rulesync:check"], {
|
|
178
|
+
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "grunt:rulesync:check"], {
|
|
167
179
|
cwd: process.cwd(),
|
|
168
180
|
stdio: "inherit",
|
|
169
181
|
});
|
|
@@ -173,7 +185,7 @@ describe("start", () => {
|
|
|
173
185
|
process.argv = ["node", "grunt", "sync-globals", "--yes"];
|
|
174
186
|
await start();
|
|
175
187
|
expect(execFileSync).toHaveBeenCalledOnce();
|
|
176
|
-
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "sync:globals"], {
|
|
188
|
+
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "grunt:sync:globals"], {
|
|
177
189
|
cwd: process.cwd(),
|
|
178
190
|
stdio: "inherit",
|
|
179
191
|
});
|
|
@@ -184,7 +196,7 @@ describe("start", () => {
|
|
|
184
196
|
await start();
|
|
185
197
|
expect(execFileSync).toHaveBeenCalledWith(
|
|
186
198
|
"npm",
|
|
187
|
-
["run", "sync:globals", "--", "--host", "grok"],
|
|
199
|
+
["run", "grunt:sync:globals", "--", "--host", "grok"],
|
|
188
200
|
{ cwd: process.cwd(), stdio: "inherit" },
|
|
189
201
|
);
|
|
190
202
|
});
|
|
@@ -194,7 +206,7 @@ describe("start", () => {
|
|
|
194
206
|
await start();
|
|
195
207
|
expect(execFileSync).toHaveBeenCalledWith(
|
|
196
208
|
"npm",
|
|
197
|
-
["run", "sync:globals:apply", "--", "--host", "grok"],
|
|
209
|
+
["run", "grunt:sync:globals:apply", "--", "--host", "grok"],
|
|
198
210
|
{ cwd: process.cwd(), stdio: "inherit" },
|
|
199
211
|
);
|
|
200
212
|
});
|
|
@@ -212,7 +224,7 @@ describe("start", () => {
|
|
|
212
224
|
await start();
|
|
213
225
|
expect(execFileSync).toHaveBeenCalledWith(
|
|
214
226
|
"npm",
|
|
215
|
-
["run", "sync:globals", "--", "--host", "claude"],
|
|
227
|
+
["run", "grunt:sync:globals", "--", "--host", "claude"],
|
|
216
228
|
{ cwd: process.cwd(), stdio: "inherit" },
|
|
217
229
|
);
|
|
218
230
|
});
|
|
@@ -220,7 +232,7 @@ describe("start", () => {
|
|
|
220
232
|
it("purge-mcps dry-run default", async () => {
|
|
221
233
|
process.argv = ["node", "grunt", "purge-mcps"];
|
|
222
234
|
await start();
|
|
223
|
-
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "purge:global-mcps"], {
|
|
235
|
+
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "grunt:purge:global-mcps"], {
|
|
224
236
|
cwd: process.cwd(),
|
|
225
237
|
stdio: "inherit",
|
|
226
238
|
});
|
|
@@ -231,20 +243,48 @@ describe("start", () => {
|
|
|
231
243
|
await start();
|
|
232
244
|
expect(execFileSync).toHaveBeenCalledWith(
|
|
233
245
|
"npm",
|
|
234
|
-
["run", "purge:global-mcps:apply"],
|
|
246
|
+
["run", "grunt:purge:global-mcps:apply"],
|
|
235
247
|
{ cwd: process.cwd(), stdio: "inherit" },
|
|
236
248
|
);
|
|
237
249
|
});
|
|
238
250
|
|
|
239
|
-
it("doctor npm-runs doctor", async () => {
|
|
251
|
+
it("doctor npm-runs grunt:doctor", async () => {
|
|
240
252
|
process.argv = ["node", "grunt", "doctor"];
|
|
241
253
|
await start();
|
|
242
|
-
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "doctor"], {
|
|
254
|
+
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "grunt:doctor"], {
|
|
243
255
|
cwd: process.cwd(),
|
|
244
256
|
stdio: "inherit",
|
|
245
257
|
});
|
|
246
258
|
});
|
|
247
259
|
|
|
260
|
+
it("setup npm-runs grunt:setup", async () => {
|
|
261
|
+
process.argv = ["node", "grunt", "setup"];
|
|
262
|
+
await start();
|
|
263
|
+
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "grunt:setup"], {
|
|
264
|
+
cwd: process.cwd(),
|
|
265
|
+
stdio: "inherit",
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("setup npm-runs grunt:setup with extra args", async () => {
|
|
270
|
+
process.argv = ["node", "grunt", "setup", "speak", "--skip-verify"];
|
|
271
|
+
await start();
|
|
272
|
+
expect(execFileSync).toHaveBeenCalledWith(
|
|
273
|
+
"npm",
|
|
274
|
+
["run", "grunt:setup", "--", "speak", "--skip-verify"],
|
|
275
|
+
{ cwd: process.cwd(), stdio: "inherit" },
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("upgrade runs init and prints reserved", async () => {
|
|
280
|
+
process.argv = ["node", "grunt", "upgrade"];
|
|
281
|
+
await start();
|
|
282
|
+
expect(init).toHaveBeenCalledOnce();
|
|
283
|
+
expect(init).toHaveBeenCalledWith(process.cwd(), { skipGlobals: false });
|
|
284
|
+
expect(chunks.join("")).toBe("reserved: browser tmp write-plan\n");
|
|
285
|
+
expect(select).not.toHaveBeenCalled();
|
|
286
|
+
});
|
|
287
|
+
|
|
248
288
|
it("unknown writes usage and exitCode 1", async () => {
|
|
249
289
|
process.argv = ["node", "grunt", "nope"];
|
|
250
290
|
await start();
|
|
@@ -273,6 +313,8 @@ describe("start", () => {
|
|
|
273
313
|
"sync-globals",
|
|
274
314
|
"purge-mcps",
|
|
275
315
|
"doctor",
|
|
316
|
+
"setup",
|
|
317
|
+
"upgrade",
|
|
276
318
|
"help",
|
|
277
319
|
"quit",
|
|
278
320
|
]);
|
|
@@ -285,7 +327,7 @@ describe("start", () => {
|
|
|
285
327
|
select.mockResolvedValue("generate");
|
|
286
328
|
process.argv = ["node", "grunt"];
|
|
287
329
|
await start();
|
|
288
|
-
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "rulesync:generate"], {
|
|
330
|
+
expect(execFileSync).toHaveBeenCalledWith("npm", ["run", "grunt:rulesync:generate"], {
|
|
289
331
|
cwd: process.cwd(),
|
|
290
332
|
stdio: "inherit",
|
|
291
333
|
});
|
package/cli/init.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
WORKSPACE_SKILLS_REL,
|
|
7
7
|
findSkillContentConflicts,
|
|
8
8
|
formatSkillConflictWarn,
|
|
9
|
+
listSkillDirNames,
|
|
9
10
|
} from "../scripts/skill-conflicts.mjs"
|
|
10
11
|
|
|
11
12
|
const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
@@ -23,29 +24,96 @@ const PRODUCT_SCRIPTS = [
|
|
|
23
24
|
"grunt-job.mjs",
|
|
24
25
|
"parse-need.mjs",
|
|
25
26
|
"persist-handoff.mjs",
|
|
27
|
+
"persist-implementation.mjs",
|
|
28
|
+
"persist-tmp.mjs",
|
|
26
29
|
"persist-plan.mjs",
|
|
27
30
|
"purge-global-mcps.mjs",
|
|
28
31
|
"scrub-spawn-prompt.mjs",
|
|
29
32
|
"scrub-text-lib.mjs",
|
|
30
33
|
"sync-global-settings.mjs",
|
|
31
34
|
"browser.mjs",
|
|
35
|
+
"speak.mjs",
|
|
36
|
+
"listen.mjs",
|
|
37
|
+
"google-workspace.mjs",
|
|
38
|
+
"setup.mjs",
|
|
32
39
|
"doctor.mjs",
|
|
33
40
|
"skill-conflicts.mjs",
|
|
34
41
|
"scrub-text",
|
|
35
42
|
]
|
|
36
43
|
|
|
37
44
|
const COPY_DIRS = [".rulesync", ".grok", ".codex", ".claude", ".agents"]
|
|
45
|
+
export const RETIRED_SKILLS = ["parent", "solo", "cascade"]
|
|
46
|
+
export const RETIRED_AGENTS = ["implementer", "thinker"]
|
|
47
|
+
/** Cumulative. Dest `scripts/<name>` deleted on init/upgrade even if no longer shipped. */
|
|
48
|
+
export const RETIRED_SCRIPTS = ["telemetry.mjs", "grunt-config.mjs"]
|
|
49
|
+
/** Repo-relative paths grunt used to ship. Deleted on init/upgrade. Consumer extras elsewhere kept. */
|
|
50
|
+
export const RETIRED_PATHS = [
|
|
51
|
+
".grok/parent.md",
|
|
52
|
+
".grok/skills/shared",
|
|
53
|
+
".rulesync/grunt.config.jsonc",
|
|
54
|
+
".rulesync/grunt.config.local.jsonc",
|
|
55
|
+
".rulesync/grunt.config.local.jsonc.example",
|
|
56
|
+
]
|
|
57
|
+
export const RESERVED_SKILLS = [
|
|
58
|
+
"ask",
|
|
59
|
+
"auto",
|
|
60
|
+
"browser",
|
|
61
|
+
"clasp",
|
|
62
|
+
"commit",
|
|
63
|
+
"commit-and-push",
|
|
64
|
+
"commit-push",
|
|
65
|
+
"commit-push-deploy",
|
|
66
|
+
"commit-push-release",
|
|
67
|
+
"explain",
|
|
68
|
+
"google-workspace",
|
|
69
|
+
"handoff",
|
|
70
|
+
"implement-plan",
|
|
71
|
+
"listen",
|
|
72
|
+
"pickup",
|
|
73
|
+
"speak",
|
|
74
|
+
"tmp",
|
|
75
|
+
"write-plan",
|
|
76
|
+
]
|
|
77
|
+
const SKILL_MIRROR_DIRS = [
|
|
78
|
+
[".rulesync", "skills"],
|
|
79
|
+
[".grok", "skills"],
|
|
80
|
+
[".claude", "skills"],
|
|
81
|
+
[".agents", "skills"],
|
|
82
|
+
]
|
|
83
|
+
const AGENT_MIRROR_DIRS = [
|
|
84
|
+
[".rulesync", "subagents"],
|
|
85
|
+
[".claude", "agents"],
|
|
86
|
+
[".grok", "agents"],
|
|
87
|
+
[".agents", "agents"],
|
|
88
|
+
[".codex", "agents"],
|
|
89
|
+
]
|
|
38
90
|
const GUARDED_MD_FILES = ["AGENTS.md", "CLAUDE.md"]
|
|
39
91
|
export const GUARDED_ROOT_FILES = ["AGENTS.md", "CLAUDE.md", "GEMINI.md"]
|
|
40
92
|
// .mcp.json is intentionally not copied here: scripts/emit-mcp-policy.mjs
|
|
41
|
-
// (run via `rulesync:generate`, see mergeClaudeSettings ordering) owns and
|
|
93
|
+
// (run via consumer `grunt:rulesync:generate`, see mergeClaudeSettings ordering) owns and
|
|
42
94
|
// merges it, so a plain-copy would just be clobbered/redundant.
|
|
95
|
+
export const GRUNT_NPM_PREFIX = "grunt:"
|
|
96
|
+
export function toGruntScriptName(k) {
|
|
97
|
+
return k.startsWith(GRUNT_NPM_PREFIX) ? k : `${GRUNT_NPM_PREFIX}${k}`
|
|
98
|
+
}
|
|
99
|
+
export const LAUNCH_SCRIPTS = {
|
|
100
|
+
antigravity: "antigravity --dangerously-skip-permissions",
|
|
101
|
+
claude: "claude --dangerously-skip-permissions",
|
|
102
|
+
codex: "codex --dangerously-bypass-approvals-and-sandbox",
|
|
103
|
+
gemini: "gemini --yolo",
|
|
104
|
+
grok: "grok --yolo",
|
|
105
|
+
}
|
|
43
106
|
const OWNED_HOOK_FILES = ["scrub-spawn-prompt.mjs", "gate-fat-tools.mjs", "orchestrate-parent.js"]
|
|
44
107
|
export const SENTINEL_BEGIN = "<!-- grunt:begin -->"
|
|
45
108
|
export const SENTINEL_END = "<!-- grunt:end -->"
|
|
46
109
|
export const MAX_GUARDED_MARKDOWN_BYTES = 2 * 1024 * 1024
|
|
47
|
-
const GRUNT_REGION_RE =
|
|
48
|
-
|
|
110
|
+
const GRUNT_REGION_RE =
|
|
111
|
+
/(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:begin -->[ \t]*\r?\n?[\s\S]*?(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:end -->[ \t]*\r?\n?/g
|
|
112
|
+
const GRUNT_INTERIOR_RE =
|
|
113
|
+
/(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:begin -->[ \t]*\r?\n?([\s\S]*?)(?:^|(?<=\n)|(?<=\r\n))[ \t]*<!-- grunt:end -->/g
|
|
114
|
+
const ORPHAN_END_RE = /(?:^|\r?\n)[ \t]*<!-- grunt:end -->[ \t]*(?=\r?\n|$)|<!-- grunt:end -->/g
|
|
115
|
+
const BEGIN_LINE_RE = /^[ \t]*<!-- grunt:begin -->[ \t]*$/
|
|
116
|
+
const END_LINE_RE = /^[ \t]*<!-- grunt:end -->[ \t]*$/
|
|
49
117
|
|
|
50
118
|
function sortKeys(obj) {
|
|
51
119
|
return Object.fromEntries(Object.keys(obj).sort().map((k) => [k, obj[k]]))
|
|
@@ -59,17 +127,58 @@ export function samePath(a, b) {
|
|
|
59
127
|
}
|
|
60
128
|
}
|
|
61
129
|
|
|
130
|
+
const GITIGNORE_ENTRIES = [{ re: /^\.tmp\/?$/, line: ".tmp/" }]
|
|
131
|
+
|
|
132
|
+
function rmQuiet(abs) {
|
|
133
|
+
fs.rmSync(abs, { recursive: true, force: true })
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function pruneRetired(dest, { pkgRoot } = {}) {
|
|
137
|
+
const root = path.resolve(dest)
|
|
138
|
+
for (const segs of SKILL_MIRROR_DIRS) {
|
|
139
|
+
for (const name of RETIRED_SKILLS) {
|
|
140
|
+
rmQuiet(path.join(root, ...segs, name))
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (pkgRoot) {
|
|
144
|
+
const packaged = new Set(
|
|
145
|
+
listSkillDirNames(path.join(path.resolve(pkgRoot), WORKSPACE_SKILLS_REL)),
|
|
146
|
+
)
|
|
147
|
+
for (const segs of SKILL_MIRROR_DIRS) {
|
|
148
|
+
for (const name of RESERVED_SKILLS) {
|
|
149
|
+
if (packaged.has(name)) continue
|
|
150
|
+
rmQuiet(path.join(root, ...segs, name))
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const segs of AGENT_MIRROR_DIRS) {
|
|
155
|
+
for (const name of RETIRED_AGENTS) {
|
|
156
|
+
rmQuiet(path.join(root, ...segs, `${name}.md`))
|
|
157
|
+
rmQuiet(path.join(root, ...segs, `${name}.toml`))
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const name of RETIRED_AGENTS) {
|
|
161
|
+
rmQuiet(path.join(root, ".gemini", "agents", name))
|
|
162
|
+
rmQuiet(path.join(root, ".grok", "roles", `${name}.toml`))
|
|
163
|
+
}
|
|
164
|
+
for (const name of RETIRED_SCRIPTS) {
|
|
165
|
+
rmQuiet(path.join(root, "scripts", name))
|
|
166
|
+
}
|
|
167
|
+
for (const rel of RETIRED_PATHS) {
|
|
168
|
+
rmQuiet(path.join(root, ...rel.split("/")))
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
62
172
|
export function mergeGitignore(dest) {
|
|
63
173
|
const gi = path.join(dest, ".gitignore")
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
fs.appendFileSync(gi, `${prefix}.tmp/\n`)
|
|
174
|
+
const text = fs.existsSync(gi) ? fs.readFileSync(gi, "utf8") : ""
|
|
175
|
+
const present = text.split(/\r?\n/)
|
|
176
|
+
const missing = GITIGNORE_ENTRIES.filter(
|
|
177
|
+
({ re }) => !present.some((line) => re.test(line)),
|
|
178
|
+
).map(({ line }) => line)
|
|
179
|
+
if (missing.length === 0) return
|
|
180
|
+
const prefix = text.length === 0 || text.endsWith("\n") ? "" : "\n"
|
|
181
|
+
fs.writeFileSync(gi, `${text}${prefix}${missing.join("\n")}\n`)
|
|
73
182
|
}
|
|
74
183
|
|
|
75
184
|
function destHasGruntSentinel(dest) {
|
|
@@ -118,18 +227,43 @@ function inspectGuardedBuffer(buf) {
|
|
|
118
227
|
}
|
|
119
228
|
|
|
120
229
|
export function extractUserMarkdown(text) {
|
|
121
|
-
|
|
230
|
+
let user = String(text).replace(cloneRe(GRUNT_REGION_RE), "")
|
|
231
|
+
const i = user.indexOf(SENTINEL_BEGIN)
|
|
232
|
+
if (i >= 0) user = user.slice(0, i)
|
|
233
|
+
user = user.replace(cloneRe(ORPHAN_END_RE), "")
|
|
234
|
+
return userRemainderIsBlank(user) ? "" : user
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function unwrapStackedMarkers(text) {
|
|
238
|
+
const lines = String(text).split(/\r?\n/)
|
|
239
|
+
let start = 0
|
|
240
|
+
let end = lines.length
|
|
241
|
+
while (start < end && BEGIN_LINE_RE.test(lines[start])) start++
|
|
242
|
+
while (end > start && lines[end - 1] === "") end--
|
|
243
|
+
while (end > start && END_LINE_RE.test(lines[end - 1])) {
|
|
244
|
+
end--
|
|
245
|
+
while (end > start && lines[end - 1] === "") end--
|
|
246
|
+
}
|
|
247
|
+
return lines.slice(start, end).join("\n")
|
|
122
248
|
}
|
|
123
249
|
|
|
124
250
|
export function extractGruntBody(text) {
|
|
251
|
+
const src = String(text)
|
|
125
252
|
const parts = []
|
|
126
253
|
const re = cloneRe(GRUNT_INTERIOR_RE)
|
|
127
254
|
let m
|
|
128
|
-
while ((m = re.exec(
|
|
129
|
-
parts.push(trimTrailingNewlines(m[1]))
|
|
255
|
+
while ((m = re.exec(src))) {
|
|
256
|
+
parts.push(trimTrailingNewlines(unwrapStackedMarkers(m[1])))
|
|
257
|
+
}
|
|
258
|
+
if (parts.length) return parts.join("\n")
|
|
259
|
+
const i = src.indexOf(SENTINEL_BEGIN)
|
|
260
|
+
if (i >= 0) {
|
|
261
|
+
return trimTrailingNewlines(
|
|
262
|
+
unwrapStackedMarkers(src.slice(i + SENTINEL_BEGIN.length).replace(/^[ \t]*\r?\n/, "")),
|
|
263
|
+
)
|
|
130
264
|
}
|
|
131
|
-
if (!
|
|
132
|
-
return
|
|
265
|
+
if (!src.includes(SENTINEL_END)) return null
|
|
266
|
+
return trimTrailingNewlines(unwrapStackedMarkers(src))
|
|
133
267
|
}
|
|
134
268
|
|
|
135
269
|
function userRemainderIsBlank(user) {
|
|
@@ -137,9 +271,10 @@ function userRemainderIsBlank(user) {
|
|
|
137
271
|
}
|
|
138
272
|
|
|
139
273
|
export function composeGuardedMarkdown(gruntBody, userRemainder, nl = "\n") {
|
|
140
|
-
const
|
|
274
|
+
const raw = extractGruntBody(gruntBody) ?? gruntBody
|
|
275
|
+
const body = toNewline(trimTrailingNewlines(raw), nl)
|
|
141
276
|
const block = `${SENTINEL_BEGIN}${nl}${body}${nl}${SENTINEL_END}${nl}`
|
|
142
|
-
const user = userRemainder == null ? "" : String(userRemainder)
|
|
277
|
+
const user = extractUserMarkdown(userRemainder == null ? "" : String(userRemainder))
|
|
143
278
|
if (!user || userRemainderIsBlank(user)) return block
|
|
144
279
|
return block + toNewline(user, nl)
|
|
145
280
|
}
|
|
@@ -184,7 +319,7 @@ export function mergeGuardedMarkdown(dest, pkgRoot, file, { alreadyInited = fals
|
|
|
184
319
|
const d = path.join(dest, file)
|
|
185
320
|
if (samePath(src, d)) return
|
|
186
321
|
const srcText = fs.readFileSync(src, "utf8")
|
|
187
|
-
return writeMergedGuardedFile(d, srcText)
|
|
322
|
+
return writeMergedGuardedFile(d, extractGruntBody(srcText) ?? srcText)
|
|
188
323
|
}
|
|
189
324
|
|
|
190
325
|
export function snapshotGuardedRoots(root) {
|
|
@@ -339,7 +474,13 @@ export function mergeClaudeSettings(destRoot, pkgRoot) {
|
|
|
339
474
|
}
|
|
340
475
|
|
|
341
476
|
function looksGruntOwnedPrefix(prefix) {
|
|
342
|
-
return /rulesync|sync:globals|^npm run /.test(
|
|
477
|
+
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(
|
|
478
|
+
prefix,
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function escapeRe(s) {
|
|
483
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
343
484
|
}
|
|
344
485
|
|
|
345
486
|
function commandCount(script) {
|
|
@@ -364,14 +505,29 @@ function extraOwnedSuffix(cur, newSrc) {
|
|
|
364
505
|
return null
|
|
365
506
|
}
|
|
366
507
|
|
|
367
|
-
function mergeScriptValue(
|
|
368
|
-
if (cur == null) return newSrc
|
|
508
|
+
function mergeScriptValue(newSrc, cur) {
|
|
369
509
|
if (cur.startsWith(newSrc)) return cur
|
|
370
510
|
const suffix = extraOwnedSuffix(cur, newSrc)
|
|
371
511
|
if (suffix != null) return newSrc + suffix
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
512
|
+
return newSrc
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function rewriteNpmRunRefs(scripts, srcKeys) {
|
|
516
|
+
const keys = srcKeys
|
|
517
|
+
.filter((k) => k !== toGruntScriptName(k))
|
|
518
|
+
.sort((a, b) => b.length - a.length)
|
|
519
|
+
if (!keys.length) return
|
|
520
|
+
for (const name of Object.keys(scripts)) {
|
|
521
|
+
let val = scripts[name]
|
|
522
|
+
if (typeof val !== "string") continue
|
|
523
|
+
for (const k of keys) {
|
|
524
|
+
val = val.replace(
|
|
525
|
+
new RegExp(`npm run ${escapeRe(k)}(?=$|\\s|;|&)`, "g"),
|
|
526
|
+
`npm run ${toGruntScriptName(k)}`,
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
scripts[name] = val
|
|
530
|
+
}
|
|
375
531
|
}
|
|
376
532
|
|
|
377
533
|
export function mergePackageJson(dest, pkgRoot) {
|
|
@@ -384,10 +540,39 @@ export function mergePackageJson(dest, pkgRoot) {
|
|
|
384
540
|
|
|
385
541
|
const srcPkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"))
|
|
386
542
|
destPkg.scripts = { ...(destPkg.scripts || {}) }
|
|
543
|
+
const srcKeys = []
|
|
387
544
|
for (const [k, v] of Object.entries(srcPkg.scripts || {})) {
|
|
388
|
-
if (k === "test" || k.endsWith(":raw")) continue
|
|
389
|
-
|
|
545
|
+
if (k === "test" || k.endsWith(":raw") || k in LAUNCH_SCRIPTS) continue
|
|
546
|
+
srcKeys.push(k)
|
|
547
|
+
const destKey = toGruntScriptName(k)
|
|
548
|
+
const current = destPkg.scripts[destKey]
|
|
549
|
+
const legacy = destPkg.scripts[k]
|
|
550
|
+
const ownedLegacy =
|
|
551
|
+
legacy != null &&
|
|
552
|
+
(legacy.startsWith(v) || extraOwnedSuffix(legacy, v) != null || looksGruntOwnedPrefix(legacy))
|
|
553
|
+
if (current != null) {
|
|
554
|
+
destPkg.scripts[destKey] = mergeScriptValue(v, current)
|
|
555
|
+
} else if (ownedLegacy) {
|
|
556
|
+
destPkg.scripts[destKey] = mergeScriptValue(v, legacy)
|
|
557
|
+
} else {
|
|
558
|
+
destPkg.scripts[destKey] = v
|
|
559
|
+
}
|
|
560
|
+
if (k !== destKey && ownedLegacy) {
|
|
561
|
+
delete destPkg.scripts[k]
|
|
562
|
+
} else if (k !== destKey && legacy != null) {
|
|
563
|
+
console.warn(
|
|
564
|
+
`script \`${k}\` left untouched (unrelated customization); writing \`${destKey}\``,
|
|
565
|
+
)
|
|
566
|
+
}
|
|
390
567
|
}
|
|
568
|
+
for (const [k, v] of Object.entries(LAUNCH_SCRIPTS)) {
|
|
569
|
+
const destKey = toGruntScriptName(k)
|
|
570
|
+
const current = destPkg.scripts[destKey]
|
|
571
|
+
destPkg.scripts[destKey] = current != null ? mergeScriptValue(v, current) : v
|
|
572
|
+
const staleKey = `grunt:yolo:${k}`
|
|
573
|
+
if (destPkg.scripts[staleKey] === v) delete destPkg.scripts[staleKey]
|
|
574
|
+
}
|
|
575
|
+
rewriteNpmRunRefs(destPkg.scripts, srcKeys)
|
|
391
576
|
destPkg.devDependencies = { ...(destPkg.devDependencies || {}) }
|
|
392
577
|
destPkg.devDependencies["smol-toml"] = srcPkg.devDependencies["smol-toml"]
|
|
393
578
|
destPkg.devDependencies["rulesync"] = srcPkg.devDependencies["rulesync"]
|
|
@@ -442,6 +627,8 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
|
|
|
442
627
|
}
|
|
443
628
|
}
|
|
444
629
|
|
|
630
|
+
pruneRetired(dest, { pkgRoot })
|
|
631
|
+
|
|
445
632
|
for (const file of GUARDED_MD_FILES) {
|
|
446
633
|
mergeGuardedMarkdown(dest, pkgRoot, file)
|
|
447
634
|
}
|
|
@@ -473,12 +660,12 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
|
|
|
473
660
|
}
|
|
474
661
|
runNpm("install", ["install"])
|
|
475
662
|
const guardedSnap = snapshotGuardedRoots(dest)
|
|
476
|
-
runNpm("generate", ["run", "rulesync:generate"])
|
|
663
|
+
runNpm("generate", ["run", toGruntScriptName("rulesync:generate")])
|
|
477
664
|
remergeGuardedRoots(dest, guardedSnap)
|
|
478
665
|
if (!skipGlobalsApply) {
|
|
479
|
-
runNpm("sync-globals", ["run", "sync:globals:apply"])
|
|
666
|
+
runNpm("sync-globals", ["run", toGruntScriptName("sync:globals:apply")])
|
|
480
667
|
}
|
|
481
668
|
withGuardedCheckInteriors(dest, () => {
|
|
482
|
-
runNpm("check", ["run", "rulesync:check"])
|
|
669
|
+
runNpm("check", ["run", toGruntScriptName("rulesync:check")])
|
|
483
670
|
})
|
|
484
671
|
}
|