@lovrozagar/grunt 0.5.2 → 0.6.1
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 -24
- package/.agents/agents/orchestrator.md +5 -50
- package/.agents/skills/ask/SKILL.md +5 -12
- package/.agents/skills/auto/SKILL.md +7 -12
- package/.agents/skills/browser/SKILL.md +4 -8
- 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 -109
- 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 +6 -10
- package/.agents/skills/write-plan/SKILL.md +5 -77
- package/.claude/agents/grunt.md +2 -26
- package/.claude/agents/orchestrator.md +12 -50
- package/.claude/skills/ask/SKILL.md +5 -12
- package/.claude/skills/auto/SKILL.md +7 -12
- package/.claude/skills/browser/SKILL.md +4 -8
- 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 -109
- 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 +6 -10
- package/.claude/skills/write-plan/SKILL.md +5 -77
- package/.codex/agents/grunt.toml +2 -26
- package/.codex/agents/orchestrator.toml +4 -50
- package/.grok/agents/grunt.md +2 -24
- package/.grok/agents/orchestrator.md +4 -48
- package/.grok/global-settings.toml +1 -3
- package/.grok/hooks/orchestrate-parent.js +190 -358
- package/.grok/skills/ask/SKILL.md +5 -12
- package/.grok/skills/auto/SKILL.md +7 -12
- package/.grok/skills/browser/SKILL.md +4 -8
- 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 -109
- 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 +6 -10
- package/.grok/skills/write-plan/SKILL.md +5 -77
- package/.rulesync/hooks.jsonc +1 -1
- package/.rulesync/mcp-policy.jsonc +0 -2
- package/.rulesync/reference/INDEX.md +30 -20
- package/.rulesync/reference/browser.md +13 -6
- package/.rulesync/reference/cascade.md +6 -86
- package/.rulesync/reference/clasp.md +21 -0
- package/.rulesync/reference/google-workspace.md +79 -0
- package/.rulesync/reference/hooks.md +7 -11
- 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 +40 -36
- package/.rulesync/reference/output.md +3 -51
- package/.rulesync/reference/plan-format.md +4 -2
- 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 -12
- package/.rulesync/reference/speak.md +59 -0
- package/.rulesync/rules/CLAUDE.md +3 -49
- package/.rulesync/rules/overview.md +9 -47
- package/.rulesync/skills/ask/SKILL.md +5 -12
- package/.rulesync/skills/auto/SKILL.md +5 -12
- 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 -119
- 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 +6 -10
- package/.rulesync/skills/write-plan/SKILL.md +5 -84
- package/.rulesync/subagents/grunt.md +2 -24
- package/.rulesync/subagents/orchestrator.md +14 -59
- package/AGENTS.md +9 -47
- package/CHANGELOG.md +67 -0
- package/CLAUDE.md +1 -49
- package/README.md +89 -80
- package/cli/grunt.mjs +21 -3
- package/cli/grunt.test.ts +47 -3
- package/cli/init.mjs +134 -234
- package/cli/init.test.ts +241 -58
- package/cli/prompt.mjs +9 -36
- package/cli/prompt.test.ts +14 -0
- package/package.json +10 -2
- package/scripts/browser.mjs +128 -7
- package/scripts/doctor.mjs +104 -10
- package/scripts/emit-gemini.mjs +2 -2
- 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/guarded-md.mjs +230 -0
- package/scripts/guarded-roots.mjs +1 -1
- package/scripts/interactive.mjs +15 -0
- package/scripts/listen.mjs +829 -0
- package/scripts/parse-need.mjs +21 -2
- package/scripts/persist-implementation.mjs +278 -0
- package/scripts/prompt.mjs +32 -0
- package/scripts/scrub-spawn-prompt.mjs +3 -50
- 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 -29
- package/.agents/skills/cascade/SKILL.md +0 -28
- package/.agents/skills/parent/SKILL.md +0 -6
- package/.agents/skills/solo/SKILL.md +0 -25
- package/.claude/agents/implementer.md +0 -37
- package/.claude/agents/thinker.md +0 -36
- package/.claude/skills/cascade/SKILL.md +0 -28
- package/.claude/skills/parent/SKILL.md +0 -6
- package/.claude/skills/solo/SKILL.md +0 -25
- package/.codex/agents/implementer.toml +0 -26
- package/.codex/agents/thinker.toml +0 -26
- package/.grok/agents/implementer.md +0 -27
- package/.grok/agents/thinker.md +0 -27
- 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 -28
- package/.grok/skills/parent/SKILL.md +0 -6
- package/.grok/skills/solo/SKILL.md +0 -25
- package/.rulesync/grunt.config.jsonc +0 -7
- package/.rulesync/skills/cascade/SKILL.md +0 -28
- package/.rulesync/skills/parent/SKILL.md +0 -6
- package/.rulesync/skills/solo/SKILL.md +0 -25
- package/.rulesync/subagents/implementer.md +0 -49
- package/.rulesync/subagents/thinker.md +0 -49
- package/scripts/grunt-config.mjs +0 -50
package/cli/init.test.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
@@ -5,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
5
6
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
7
|
import {
|
|
7
8
|
MAX_GUARDED_MARKDOWN_BYTES,
|
|
9
|
+
PRODUCT_SCRIPTS,
|
|
8
10
|
composeGuardedMarkdown,
|
|
9
11
|
destAlreadyInited,
|
|
10
12
|
extractGruntBody,
|
|
@@ -15,6 +17,12 @@ import {
|
|
|
15
17
|
guardedMarkdownDrift,
|
|
16
18
|
healGuardedRootFile,
|
|
17
19
|
init,
|
|
20
|
+
pruneRetired,
|
|
21
|
+
RETIRED_SKILLS,
|
|
22
|
+
RETIRED_AGENTS,
|
|
23
|
+
RETIRED_SCRIPTS,
|
|
24
|
+
RETIRED_PATHS,
|
|
25
|
+
RESERVED_SKILLS,
|
|
18
26
|
LAUNCH_SCRIPTS,
|
|
19
27
|
mergeClaudeSettings,
|
|
20
28
|
mergeGitignore,
|
|
@@ -30,7 +38,6 @@ import {
|
|
|
30
38
|
writeMergedGuardedFile,
|
|
31
39
|
} from "./init.mjs";
|
|
32
40
|
import { runGuardedRoots } from "../scripts/guarded-roots.mjs";
|
|
33
|
-
import { stripJsonc } from "../scripts/grunt-config.mjs";
|
|
34
41
|
|
|
35
42
|
const tmpDirs: string[] = [];
|
|
36
43
|
afterEach(() => {
|
|
@@ -46,37 +53,9 @@ function tmp(prefix: string) {
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
const COPY_DIRS = [".rulesync", ".grok", ".codex", ".claude", ".agents"];
|
|
49
|
-
const GRUNT_CONFIG_REL = path.join(".rulesync", "grunt.config.jsonc");
|
|
50
|
-
const GRUNT_CONFIG_DEFAULTS = {
|
|
51
|
-
version: 1,
|
|
52
|
-
leftoverGate: "ask",
|
|
53
|
-
spawnMode: "cascade",
|
|
54
|
-
};
|
|
55
56
|
const GUARDED_MD_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
56
|
-
const PRODUCT_FILES =
|
|
57
|
-
|
|
58
|
-
"emit-agent-shell-tools.mjs",
|
|
59
|
-
"emit-gemini.mjs",
|
|
60
|
-
"emit-maps.mjs",
|
|
61
|
-
"guarded-roots.mjs",
|
|
62
|
-
"emit-mcp-policy.mjs",
|
|
63
|
-
"gate-fat-tools.mjs",
|
|
64
|
-
"hooks-union.mjs",
|
|
65
|
-
"pipeline.mjs",
|
|
66
|
-
"grunt-job.mjs",
|
|
67
|
-
"grunt-config.mjs",
|
|
68
|
-
"parse-need.mjs",
|
|
69
|
-
"persist-handoff.mjs",
|
|
70
|
-
"persist-tmp.mjs",
|
|
71
|
-
"persist-plan.mjs",
|
|
72
|
-
"purge-global-mcps.mjs",
|
|
73
|
-
"scrub-spawn-prompt.mjs",
|
|
74
|
-
"scrub-text-lib.mjs",
|
|
75
|
-
"sync-global-settings.mjs",
|
|
76
|
-
"browser.mjs",
|
|
77
|
-
"doctor.mjs",
|
|
78
|
-
"skill-conflicts.mjs",
|
|
79
|
-
];
|
|
57
|
+
const PRODUCT_FILES = PRODUCT_SCRIPTS.filter((name) => name !== "scrub-text");
|
|
58
|
+
const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
80
59
|
|
|
81
60
|
const SRC_CLAUDE_SETTINGS = {
|
|
82
61
|
permissions: {
|
|
@@ -118,12 +97,6 @@ const SRC_CLAUDE_SETTINGS = {
|
|
|
118
97
|
enabledMcpjsonServers: [],
|
|
119
98
|
};
|
|
120
99
|
|
|
121
|
-
function readGruntConfig(root: string) {
|
|
122
|
-
const p = path.join(root, GRUNT_CONFIG_REL);
|
|
123
|
-
expect(fs.existsSync(p)).toBe(true);
|
|
124
|
-
return JSON.parse(stripJsonc(fs.readFileSync(p, "utf8")));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
100
|
function stubPkgRoot(pkg: Record<string, unknown> = {
|
|
128
101
|
name: "fixture-pkg",
|
|
129
102
|
scripts: {
|
|
@@ -143,10 +116,6 @@ function stubPkgRoot(pkg: Record<string, unknown> = {
|
|
|
143
116
|
fs.mkdirSync(path.join(root, d));
|
|
144
117
|
fs.writeFileSync(path.join(root, d, "marker"), d);
|
|
145
118
|
}
|
|
146
|
-
fs.writeFileSync(
|
|
147
|
-
path.join(root, GRUNT_CONFIG_REL),
|
|
148
|
-
`${JSON.stringify(GRUNT_CONFIG_DEFAULTS, null, 2)}\n`,
|
|
149
|
-
);
|
|
150
119
|
fs.writeFileSync(
|
|
151
120
|
path.join(root, ".claude", "settings.json"),
|
|
152
121
|
`${JSON.stringify(SRC_CLAUDE_SETTINGS, null, 2)}\n`,
|
|
@@ -179,18 +148,22 @@ describe("samePath", () => {
|
|
|
179
148
|
});
|
|
180
149
|
});
|
|
181
150
|
|
|
151
|
+
const GI_TMP = ".tmp/\n";
|
|
152
|
+
|
|
182
153
|
describe("mergeGitignore", () => {
|
|
183
154
|
it("creates .tmp/ when missing", () => {
|
|
184
155
|
const dest = tmp("gi-miss-");
|
|
185
156
|
mergeGitignore(dest);
|
|
186
|
-
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
157
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(GI_TMP);
|
|
187
158
|
});
|
|
188
159
|
|
|
189
|
-
it("noop when .tmp/ present", () => {
|
|
160
|
+
it("noop when .tmp/ already present", () => {
|
|
190
161
|
const dest = tmp("gi-slash-");
|
|
191
162
|
fs.writeFileSync(path.join(dest, ".gitignore"), "foo\n.tmp/\nbar\n");
|
|
192
163
|
mergeGitignore(dest);
|
|
193
|
-
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
164
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
165
|
+
"foo\n.tmp/\nbar\n",
|
|
166
|
+
);
|
|
194
167
|
});
|
|
195
168
|
|
|
196
169
|
it("noop when .tmp present", () => {
|
|
@@ -200,12 +173,21 @@ describe("mergeGitignore", () => {
|
|
|
200
173
|
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(".tmp\n");
|
|
201
174
|
});
|
|
202
175
|
|
|
176
|
+
it("noop when .tmp/ present in mixed file", () => {
|
|
177
|
+
const dest = tmp("gi-both-");
|
|
178
|
+
fs.writeFileSync(path.join(dest, ".gitignore"), `foo\n${GI_TMP}bar\n`);
|
|
179
|
+
mergeGitignore(dest);
|
|
180
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
181
|
+
`foo\n${GI_TMP}bar\n`,
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
203
185
|
it("appends after trailing newline", () => {
|
|
204
186
|
const dest = tmp("gi-nl-");
|
|
205
187
|
fs.writeFileSync(path.join(dest, ".gitignore"), "node_modules/\n");
|
|
206
188
|
mergeGitignore(dest);
|
|
207
189
|
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
208
|
-
|
|
190
|
+
`node_modules/\n${GI_TMP}`,
|
|
209
191
|
);
|
|
210
192
|
});
|
|
211
193
|
|
|
@@ -214,7 +196,7 @@ describe("mergeGitignore", () => {
|
|
|
214
196
|
fs.writeFileSync(path.join(dest, ".gitignore"), "node_modules/");
|
|
215
197
|
mergeGitignore(dest);
|
|
216
198
|
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
217
|
-
|
|
199
|
+
`node_modules/\n${GI_TMP}`,
|
|
218
200
|
);
|
|
219
201
|
});
|
|
220
202
|
|
|
@@ -222,7 +204,7 @@ describe("mergeGitignore", () => {
|
|
|
222
204
|
const dest = tmp("gi-empty-");
|
|
223
205
|
fs.writeFileSync(path.join(dest, ".gitignore"), "");
|
|
224
206
|
mergeGitignore(dest);
|
|
225
|
-
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(
|
|
207
|
+
expect(fs.readFileSync(path.join(dest, ".gitignore"), "utf8")).toBe(GI_TMP);
|
|
226
208
|
});
|
|
227
209
|
});
|
|
228
210
|
|
|
@@ -1550,9 +1532,9 @@ describe("init", () => {
|
|
|
1550
1532
|
if (d === ".claude") continue;
|
|
1551
1533
|
expect(fs.readFileSync(path.join(dest, d, "marker"), "utf8")).toBe(d);
|
|
1552
1534
|
}
|
|
1553
|
-
expect(
|
|
1554
|
-
expect(PRODUCT_FILES).toContain("grunt-config.mjs");
|
|
1555
|
-
expect(fs.
|
|
1535
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1536
|
+
expect(PRODUCT_FILES).not.toContain("grunt-config.mjs");
|
|
1537
|
+
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1556
1538
|
expect(fs.readFileSync(path.join(dest, ".claude", "marker"), "utf8")).toBe(".claude");
|
|
1557
1539
|
const settings = JSON.parse(
|
|
1558
1540
|
fs.readFileSync(path.join(dest, ".claude", "settings.json"), "utf8"),
|
|
@@ -1626,6 +1608,109 @@ describe("init", () => {
|
|
|
1626
1608
|
expect(fs.readFileSync(path.join(pkgRoot, ".gitignore"), "utf8")).toBe(".tmp/\n");
|
|
1627
1609
|
});
|
|
1628
1610
|
|
|
1611
|
+
it("prunes retired 0.5 skills and agents on dest", () => {
|
|
1612
|
+
const pkgRoot = stubPkgRoot();
|
|
1613
|
+
const dest = tmp("grunt-prune-");
|
|
1614
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "solo"), { recursive: true });
|
|
1615
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "solo", "SKILL.md"), "old");
|
|
1616
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "parent"), { recursive: true });
|
|
1617
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "cascade"), { recursive: true });
|
|
1618
|
+
fs.mkdirSync(path.join(dest, ".claude", "agents"), { recursive: true });
|
|
1619
|
+
fs.writeFileSync(path.join(dest, ".claude", "agents", "implementer.md"), "old");
|
|
1620
|
+
fs.writeFileSync(path.join(dest, ".claude", "agents", "thinker.md"), "old");
|
|
1621
|
+
expect(RETIRED_SKILLS).toEqual(["parent", "solo", "cascade"]);
|
|
1622
|
+
expect(RETIRED_AGENTS).toEqual(["implementer", "thinker"]);
|
|
1623
|
+
expect(RETIRED_SCRIPTS).toEqual(["telemetry.mjs", "grunt-config.mjs"]);
|
|
1624
|
+
expect(RETIRED_PATHS).toEqual([
|
|
1625
|
+
".grok/parent.md",
|
|
1626
|
+
".grok/skills/shared",
|
|
1627
|
+
".rulesync/grunt.config.jsonc",
|
|
1628
|
+
".rulesync/grunt.config.local.jsonc",
|
|
1629
|
+
".rulesync/grunt.config.local.jsonc.example",
|
|
1630
|
+
]);
|
|
1631
|
+
expect(RESERVED_SKILLS).toEqual([
|
|
1632
|
+
"ask",
|
|
1633
|
+
"auto",
|
|
1634
|
+
"browser",
|
|
1635
|
+
"clasp",
|
|
1636
|
+
"commit",
|
|
1637
|
+
"commit-and-push",
|
|
1638
|
+
"commit-push",
|
|
1639
|
+
"commit-push-deploy",
|
|
1640
|
+
"commit-push-release",
|
|
1641
|
+
"explain",
|
|
1642
|
+
"google-workspace",
|
|
1643
|
+
"handoff",
|
|
1644
|
+
"implement-plan",
|
|
1645
|
+
"listen",
|
|
1646
|
+
"pickup",
|
|
1647
|
+
"speak",
|
|
1648
|
+
"tmp",
|
|
1649
|
+
"write-plan",
|
|
1650
|
+
]);
|
|
1651
|
+
expect(RESERVED_SKILLS).not.toEqual(expect.arrayContaining(RETIRED_SKILLS));
|
|
1652
|
+
init(dest, { pkgRoot, execFileSync: vi.fn() });
|
|
1653
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "solo"))).toBe(false);
|
|
1654
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "parent"))).toBe(false);
|
|
1655
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "cascade"))).toBe(false);
|
|
1656
|
+
expect(fs.existsSync(path.join(dest, ".claude", "agents", "implementer.md"))).toBe(
|
|
1657
|
+
false,
|
|
1658
|
+
);
|
|
1659
|
+
expect(fs.existsSync(path.join(dest, ".claude", "agents", "thinker.md"))).toBe(false);
|
|
1660
|
+
pruneRetired(dest, { pkgRoot });
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
it("prunes retired scripts/paths, reserved-not-packaged skills, and grok roles; keeps consumer extras", () => {
|
|
1664
|
+
const pkgRoot = stubPkgRoot();
|
|
1665
|
+
fs.mkdirSync(path.join(pkgRoot, ".rulesync", "skills", "tmp"), { recursive: true });
|
|
1666
|
+
fs.writeFileSync(path.join(pkgRoot, ".rulesync", "skills", "tmp", "SKILL.md"), "pkg tmp");
|
|
1667
|
+
const dest = tmp("grunt-prune-extra-");
|
|
1668
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "tmp"), { recursive: true });
|
|
1669
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "tmp", "SKILL.md"), "old tmp");
|
|
1670
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "browser"), { recursive: true });
|
|
1671
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "browser", "SKILL.md"), "stale reserved");
|
|
1672
|
+
fs.mkdirSync(path.join(dest, ".rulesync", "skills", "acme"), { recursive: true });
|
|
1673
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "skills", "acme", "SKILL.md"), "consumer");
|
|
1674
|
+
fs.mkdirSync(path.join(dest, ".grok", "roles"), { recursive: true });
|
|
1675
|
+
fs.writeFileSync(path.join(dest, ".grok", "roles", "implementer.toml"), "old");
|
|
1676
|
+
fs.writeFileSync(path.join(dest, ".grok", "roles", "thinker.toml"), "old");
|
|
1677
|
+
fs.writeFileSync(path.join(dest, ".grok", "parent.md"), "pointer");
|
|
1678
|
+
fs.mkdirSync(path.join(dest, ".grok", "skills", "shared"), { recursive: true });
|
|
1679
|
+
fs.mkdirSync(path.join(dest, "scripts"), { recursive: true });
|
|
1680
|
+
fs.writeFileSync(path.join(dest, "scripts", "telemetry.mjs"), "old telemetry");
|
|
1681
|
+
fs.writeFileSync(path.join(dest, "scripts", "grunt-config.mjs"), "old config");
|
|
1682
|
+
fs.writeFileSync(path.join(dest, "scripts", "mine.mjs"), "keep");
|
|
1683
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "grunt.config.jsonc"), '{"version":1}\n');
|
|
1684
|
+
fs.writeFileSync(path.join(dest, ".rulesync", "grunt.config.local.jsonc"), '{"sessionGate":"ask"}\n');
|
|
1685
|
+
fs.writeFileSync(
|
|
1686
|
+
path.join(dest, ".rulesync", "grunt.config.local.jsonc.example"),
|
|
1687
|
+
'{"sessionGate":"ask"}\n',
|
|
1688
|
+
);
|
|
1689
|
+
fs.mkdirSync(path.join(dest, ".gemini", "agents", "implementer"), { recursive: true });
|
|
1690
|
+
fs.writeFileSync(path.join(dest, ".gemini", "agents", "implementer", "agent.md"), "old");
|
|
1691
|
+
init(dest, { pkgRoot, execFileSync: vi.fn() });
|
|
1692
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "skills", "tmp", "SKILL.md"), "utf8")).toBe(
|
|
1693
|
+
"pkg tmp",
|
|
1694
|
+
);
|
|
1695
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "skills", "browser"))).toBe(false);
|
|
1696
|
+
expect(fs.readFileSync(path.join(dest, ".rulesync", "skills", "acme", "SKILL.md"), "utf8")).toBe(
|
|
1697
|
+
"consumer",
|
|
1698
|
+
);
|
|
1699
|
+
expect(fs.existsSync(path.join(dest, ".grok", "roles", "implementer.toml"))).toBe(false);
|
|
1700
|
+
expect(fs.existsSync(path.join(dest, ".grok", "roles", "thinker.toml"))).toBe(false);
|
|
1701
|
+
expect(fs.existsSync(path.join(dest, ".grok", "parent.md"))).toBe(false);
|
|
1702
|
+
expect(fs.existsSync(path.join(dest, ".grok", "skills", "shared"))).toBe(false);
|
|
1703
|
+
expect(fs.existsSync(path.join(dest, "scripts", "telemetry.mjs"))).toBe(false);
|
|
1704
|
+
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1705
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1706
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.local.jsonc"))).toBe(false);
|
|
1707
|
+
expect(
|
|
1708
|
+
fs.existsSync(path.join(dest, ".rulesync", "grunt.config.local.jsonc.example")),
|
|
1709
|
+
).toBe(false);
|
|
1710
|
+
expect(fs.readFileSync(path.join(dest, "scripts", "mine.mjs"), "utf8")).toBe("keep");
|
|
1711
|
+
expect(fs.existsSync(path.join(dest, ".gemini", "agents", "implementer"))).toBe(false);
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1629
1714
|
it("omitted pkgRoot uses built-in PKG_ROOT; exec mocked", () => {
|
|
1630
1715
|
const dest = tmp("grunt-realroot-");
|
|
1631
1716
|
const exec = vi.fn();
|
|
@@ -1638,9 +1723,12 @@ describe("init", () => {
|
|
|
1638
1723
|
expect(claude.split("<!-- grunt:begin -->")).toHaveLength(2);
|
|
1639
1724
|
expect(claude.split("<!-- grunt:end -->")).toHaveLength(2);
|
|
1640
1725
|
expect(fs.existsSync(path.join(dest, "scripts", "scrub-text"))).toBe(true);
|
|
1641
|
-
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(
|
|
1726
|
+
expect(fs.existsSync(path.join(dest, "scripts", "grunt-config.mjs"))).toBe(false);
|
|
1727
|
+
expect(fs.existsSync(path.join(dest, "scripts", "speak.mjs"))).toBe(true);
|
|
1728
|
+
expect(fs.existsSync(path.join(dest, "scripts", "listen.mjs"))).toBe(true);
|
|
1729
|
+
expect(fs.existsSync(path.join(dest, "scripts", "google-workspace.mjs"))).toBe(true);
|
|
1642
1730
|
expect(fs.existsSync(path.join(dest, ".claude", "settings.json"))).toBe(true);
|
|
1643
|
-
expect(
|
|
1731
|
+
expect(fs.existsSync(path.join(dest, ".rulesync", "grunt.config.jsonc"))).toBe(false);
|
|
1644
1732
|
expect(exec.mock.calls.map((c) => c[1])).toEqual([
|
|
1645
1733
|
["install"],
|
|
1646
1734
|
["run", "grunt:rulesync:generate"],
|
|
@@ -1829,25 +1917,25 @@ describe("init", () => {
|
|
|
1829
1917
|
|
|
1830
1918
|
it("warns when dest skill differs from packaged then force-overwrites; keeps extras", () => {
|
|
1831
1919
|
const pkgRoot = stubPkgRoot();
|
|
1832
|
-
const pkgSkill = path.join(pkgRoot, ".rulesync", "skills", "
|
|
1920
|
+
const pkgSkill = path.join(pkgRoot, ".rulesync", "skills", "explain");
|
|
1833
1921
|
fs.mkdirSync(pkgSkill, { recursive: true });
|
|
1834
|
-
fs.writeFileSync(path.join(pkgSkill, "SKILL.md"), "grunt-
|
|
1922
|
+
fs.writeFileSync(path.join(pkgSkill, "SKILL.md"), "grunt-explain\n");
|
|
1835
1923
|
const dest = tmp("grunt-sk-conflict-");
|
|
1836
|
-
const destSkill = path.join(dest, ".rulesync", "skills", "
|
|
1924
|
+
const destSkill = path.join(dest, ".rulesync", "skills", "explain");
|
|
1837
1925
|
const destExtra = path.join(dest, ".rulesync", "skills", "my-extra");
|
|
1838
1926
|
fs.mkdirSync(destSkill, { recursive: true });
|
|
1839
1927
|
fs.mkdirSync(destExtra, { recursive: true });
|
|
1840
|
-
fs.writeFileSync(path.join(destSkill, "SKILL.md"), "custom-
|
|
1928
|
+
fs.writeFileSync(path.join(destSkill, "SKILL.md"), "custom-explain\n");
|
|
1841
1929
|
fs.writeFileSync(path.join(destExtra, "SKILL.md"), "keep-me\n");
|
|
1842
1930
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1843
1931
|
init(dest, { pkgRoot, execFileSync: vi.fn(), skipGlobals: true });
|
|
1844
1932
|
expect(
|
|
1845
1933
|
warnSpy.mock.calls.some(
|
|
1846
|
-
(c) => String(c[0]).includes("
|
|
1934
|
+
(c) => String(c[0]).includes("explain") && String(c[0]).includes("re-init overwrites"),
|
|
1847
1935
|
),
|
|
1848
1936
|
).toBe(true);
|
|
1849
1937
|
warnSpy.mockRestore();
|
|
1850
|
-
expect(fs.readFileSync(path.join(destSkill, "SKILL.md"), "utf8")).toBe("grunt-
|
|
1938
|
+
expect(fs.readFileSync(path.join(destSkill, "SKILL.md"), "utf8")).toBe("grunt-explain\n");
|
|
1851
1939
|
expect(fs.readFileSync(path.join(destExtra, "SKILL.md"), "utf8")).toBe("keep-me\n");
|
|
1852
1940
|
});
|
|
1853
1941
|
});
|
|
@@ -1884,3 +1972,98 @@ describe("destAlreadyInited / shouldAutoSkipGlobals", () => {
|
|
|
1884
1972
|
});
|
|
1885
1973
|
});
|
|
1886
1974
|
|
|
1975
|
+
function spawnDest(dest: string, args: string[], env: NodeJS.ProcessEnv = process.env) {
|
|
1976
|
+
return spawnSync(process.execPath, args, {
|
|
1977
|
+
cwd: dest,
|
|
1978
|
+
encoding: "utf8",
|
|
1979
|
+
env,
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
const PARENT_IMPORT_RE =
|
|
1984
|
+
/(?:from\s+|import\s*\(\s*)["'`](\.\.[^"'`]*)["'`]/g;
|
|
1985
|
+
const RELATIVE_IMPORT_RE =
|
|
1986
|
+
/(?:from\s+|import\s*\(\s*)["'`](\.[^"'`]+)["'`]/g;
|
|
1987
|
+
|
|
1988
|
+
function relativeSpecs(text: string, re: RegExp) {
|
|
1989
|
+
const out: string[] = [];
|
|
1990
|
+
const copy = new RegExp(re.source, "g");
|
|
1991
|
+
let m: RegExpExecArray | null;
|
|
1992
|
+
while ((m = copy.exec(text))) out.push(m[1]);
|
|
1993
|
+
return out;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
describe("product scripts are consumer-safe", () => {
|
|
1997
|
+
it("package.json files lists every PRODUCT_SCRIPTS entry", () => {
|
|
1998
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8"));
|
|
1999
|
+
for (const name of PRODUCT_SCRIPTS) {
|
|
2000
|
+
expect(pkg.files).toContain(`scripts/${name}`);
|
|
2001
|
+
}
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
it("do not parent-import (from or import()) — init copies scripts/ only", () => {
|
|
2005
|
+
for (const name of PRODUCT_FILES) {
|
|
2006
|
+
const text = fs.readFileSync(path.join(PKG_ROOT, "scripts", name), "utf8");
|
|
2007
|
+
expect(relativeSpecs(text, PARENT_IMPORT_RE), name).toEqual([]);
|
|
2008
|
+
}
|
|
2009
|
+
});
|
|
2010
|
+
|
|
2011
|
+
it("repro: dest node ./scripts/guarded-roots.mjs generate misses ../cli/init.mjs", () => {
|
|
2012
|
+
const dest = tmp("repro-cli-miss-");
|
|
2013
|
+
fs.mkdirSync(path.join(dest, "scripts"));
|
|
2014
|
+
fs.writeFileSync(
|
|
2015
|
+
path.join(dest, "scripts", "broken.mjs"),
|
|
2016
|
+
'import { snapshotGuardedRoots } from "../cli/init.mjs";\n',
|
|
2017
|
+
);
|
|
2018
|
+
const r = spawnDest(dest, ["./scripts/broken.mjs", "generate"]);
|
|
2019
|
+
expect(r.status).not.toBe(0);
|
|
2020
|
+
expect(r.stderr).toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
2021
|
+
expect(r.stderr).toMatch(/cli\/init\.mjs/);
|
|
2022
|
+
});
|
|
2023
|
+
|
|
2024
|
+
it("after init, copied scripts' relative imports exist and generate loads", () => {
|
|
2025
|
+
const dest = tmp("init-generate-");
|
|
2026
|
+
init(dest, {
|
|
2027
|
+
pkgRoot: PKG_ROOT,
|
|
2028
|
+
execFileSync: vi.fn(),
|
|
2029
|
+
skipGlobals: true,
|
|
2030
|
+
applyGlobals: false,
|
|
2031
|
+
});
|
|
2032
|
+
expect(fs.existsSync(path.join(dest, "cli"))).toBe(false);
|
|
2033
|
+
expect(fs.existsSync(path.join(dest, "scripts", "guarded-roots.mjs"))).toBe(true);
|
|
2034
|
+
|
|
2035
|
+
for (const name of PRODUCT_FILES) {
|
|
2036
|
+
const file = path.join(dest, "scripts", name);
|
|
2037
|
+
expect(fs.existsSync(file), name).toBe(true);
|
|
2038
|
+
const text = fs.readFileSync(file, "utf8");
|
|
2039
|
+
for (const spec of relativeSpecs(text, RELATIVE_IMPORT_RE)) {
|
|
2040
|
+
const resolved = path.resolve(path.dirname(file), spec);
|
|
2041
|
+
expect(fs.existsSync(resolved), `${name} -> ${spec}`).toBe(true);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
const loaded = spawnDest(dest, ["./scripts/guarded-roots.mjs"]);
|
|
2046
|
+
expect(loaded.status).toBe(1);
|
|
2047
|
+
expect(loaded.stderr).toBe("usage: guarded-roots.mjs generate|check|watch\n");
|
|
2048
|
+
expect(loaded.stderr).not.toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
2049
|
+
|
|
2050
|
+
const gen = spawnDest(dest, ["./scripts/guarded-roots.mjs", "generate"], {
|
|
2051
|
+
...process.env,
|
|
2052
|
+
PATH: ["/usr/bin", "/bin"].join(path.delimiter),
|
|
2053
|
+
});
|
|
2054
|
+
expect(gen.stderr).not.toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
2055
|
+
expect(gen.stderr).not.toMatch(/cli\/init\.mjs/);
|
|
2056
|
+
expect(`${gen.stderr}${gen.stdout}`).toMatch(/rulesync|spawn|not found|ENOENT/i);
|
|
2057
|
+
|
|
2058
|
+
for (const name of ["guarded-roots.mjs", "emit-gemini.mjs", "setup.mjs"]) {
|
|
2059
|
+
const r = spawnDest(dest, [
|
|
2060
|
+
"--input-type=module",
|
|
2061
|
+
"-e",
|
|
2062
|
+
`import(${JSON.stringify(`./scripts/${name}`)})`,
|
|
2063
|
+
]);
|
|
2064
|
+
expect(r.status, `${name} load\n${r.stderr}`).toBe(0);
|
|
2065
|
+
expect(r.stderr, name).not.toMatch(/ERR_MODULE_NOT_FOUND/);
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
});
|
|
2069
|
+
|
package/cli/prompt.mjs
CHANGED
|
@@ -1,36 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const ci = env.CI
|
|
11
|
-
if (ci != null && ci !== "" && ci !== "0" && ci !== "false") return false
|
|
12
|
-
for (const a of argv) {
|
|
13
|
-
if (a === "--yes" || a === "-y" || a === "--non-interactive") return false
|
|
14
|
-
}
|
|
15
|
-
return true
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function bailIfCancel(value) {
|
|
19
|
-
if (clack.isCancel(value)) {
|
|
20
|
-
clack.cancel("Aborted")
|
|
21
|
-
process.exit(0)
|
|
22
|
-
}
|
|
23
|
-
return value
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function select(opts) {
|
|
27
|
-
return bailIfCancel(await clack.select(opts))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function confirm(opts) {
|
|
31
|
-
return bailIfCancel(await clack.confirm(opts))
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function spinner() {
|
|
35
|
-
return clack.spinner()
|
|
36
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
bailIfCancel,
|
|
3
|
+
confirm,
|
|
4
|
+
isInteractive,
|
|
5
|
+
password,
|
|
6
|
+
select,
|
|
7
|
+
spinner,
|
|
8
|
+
text,
|
|
9
|
+
} from "../scripts/prompt.mjs"
|
package/cli/prompt.test.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
2
2
|
|
|
3
3
|
const clackSelect = vi.hoisted(() => vi.fn());
|
|
4
4
|
const clackConfirm = vi.hoisted(() => vi.fn());
|
|
5
|
+
const clackText = vi.hoisted(() => vi.fn());
|
|
6
|
+
const clackPassword = vi.hoisted(() => vi.fn());
|
|
5
7
|
const clackSpinner = vi.hoisted(() => vi.fn());
|
|
6
8
|
const clackIsCancel = vi.hoisted(() => vi.fn());
|
|
7
9
|
const clackCancel = vi.hoisted(() => vi.fn());
|
|
@@ -9,6 +11,8 @@ const clackCancel = vi.hoisted(() => vi.fn());
|
|
|
9
11
|
vi.mock("@clack/prompts", () => ({
|
|
10
12
|
select: clackSelect,
|
|
11
13
|
confirm: clackConfirm,
|
|
14
|
+
text: clackText,
|
|
15
|
+
password: clackPassword,
|
|
12
16
|
spinner: clackSpinner,
|
|
13
17
|
isCancel: clackIsCancel,
|
|
14
18
|
cancel: clackCancel,
|
|
@@ -18,8 +22,10 @@ import {
|
|
|
18
22
|
bailIfCancel,
|
|
19
23
|
confirm,
|
|
20
24
|
isInteractive,
|
|
25
|
+
password,
|
|
21
26
|
select,
|
|
22
27
|
spinner,
|
|
28
|
+
text,
|
|
23
29
|
} from "./prompt.mjs";
|
|
24
30
|
|
|
25
31
|
function tty(isTTY: boolean) {
|
|
@@ -196,4 +202,12 @@ describe("clack wrappers", () => {
|
|
|
196
202
|
expect(spinner()).toBe(s);
|
|
197
203
|
expect(clackSpinner).toHaveBeenCalledOnce();
|
|
198
204
|
});
|
|
205
|
+
|
|
206
|
+
it("text and password bail then return", async () => {
|
|
207
|
+
clackText.mockResolvedValue("proj");
|
|
208
|
+
clackPassword.mockResolvedValue("sk_x");
|
|
209
|
+
clackIsCancel.mockReturnValue(false);
|
|
210
|
+
await expect(text({ message: "Project ID" })).resolves.toBe("proj");
|
|
211
|
+
await expect(password({ message: "API key" })).resolves.toBe("sk_x");
|
|
212
|
+
});
|
|
199
213
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovrozagar/grunt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|
|
@@ -28,15 +28,16 @@
|
|
|
28
28
|
"scripts/emit-agent-shell-tools.mjs",
|
|
29
29
|
"scripts/emit-gemini.mjs",
|
|
30
30
|
"scripts/emit-maps.mjs",
|
|
31
|
+
"scripts/guarded-md.mjs",
|
|
31
32
|
"scripts/guarded-roots.mjs",
|
|
32
33
|
"scripts/emit-mcp-policy.mjs",
|
|
33
34
|
"scripts/gate-fat-tools.mjs",
|
|
34
35
|
"scripts/hooks-union.mjs",
|
|
35
36
|
"scripts/pipeline.mjs",
|
|
36
37
|
"scripts/grunt-job.mjs",
|
|
37
|
-
"scripts/grunt-config.mjs",
|
|
38
38
|
"scripts/parse-need.mjs",
|
|
39
39
|
"scripts/persist-handoff.mjs",
|
|
40
|
+
"scripts/persist-implementation.mjs",
|
|
40
41
|
"scripts/persist-tmp.mjs",
|
|
41
42
|
"scripts/persist-plan.mjs",
|
|
42
43
|
"scripts/purge-global-mcps.mjs",
|
|
@@ -44,6 +45,12 @@
|
|
|
44
45
|
"scripts/scrub-text-lib.mjs",
|
|
45
46
|
"scripts/sync-global-settings.mjs",
|
|
46
47
|
"scripts/browser.mjs",
|
|
48
|
+
"scripts/speak.mjs",
|
|
49
|
+
"scripts/listen.mjs",
|
|
50
|
+
"scripts/google-workspace.mjs",
|
|
51
|
+
"scripts/interactive.mjs",
|
|
52
|
+
"scripts/prompt.mjs",
|
|
53
|
+
"scripts/setup.mjs",
|
|
47
54
|
"scripts/doctor.mjs",
|
|
48
55
|
"scripts/skill-conflicts.mjs",
|
|
49
56
|
"scripts/scrub-text",
|
|
@@ -73,6 +80,7 @@
|
|
|
73
80
|
"hooks:union": "node scripts/hooks-union.mjs",
|
|
74
81
|
"hooks:check": "node scripts/hooks-union.mjs --check",
|
|
75
82
|
"doctor": "node ./scripts/doctor.mjs",
|
|
83
|
+
"setup": "node ./scripts/setup.mjs",
|
|
76
84
|
"rulesync:doctor": "rulesync doctor",
|
|
77
85
|
"sync:globals": "node scripts/sync-global-settings.mjs",
|
|
78
86
|
"sync:globals:check": "node scripts/check-globals.mjs",
|