@lovrozagar/grunt 0.3.7 → 0.3.9
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/orchestrator.md +9 -9
- package/.agents/hooks.json +1 -1
- package/.agents/skills/cascade/SKILL.md +26 -0
- package/.agents/skills/explain/SKILL.md +5 -3
- package/.agents/skills/handoff/SKILL.md +3 -2
- package/.agents/skills/parent/SKILL.md +3 -8
- package/.agents/skills/solo/SKILL.md +2 -0
- package/.claude/agents/orchestrator.md +10 -10
- package/.claude/settings.json +1 -1
- package/.claude/skills/cascade/SKILL.md +26 -0
- package/.claude/skills/explain/SKILL.md +5 -3
- package/.claude/skills/handoff/SKILL.md +3 -2
- package/.claude/skills/parent/SKILL.md +3 -8
- package/.claude/skills/solo/SKILL.md +2 -0
- package/.codex/agents/orchestrator.toml +8 -5
- package/.codex/hooks.json +1 -1
- package/.grok/agents/orchestrator.md +9 -6
- package/.grok/hooks/orchestrate-parent.js +2 -1
- package/.grok/skills/cascade/SKILL.md +26 -0
- package/.grok/skills/explain/SKILL.md +5 -4
- package/.grok/skills/handoff/SKILL.md +3 -2
- package/.grok/skills/implement-plan/SKILL.md +8 -12
- package/.grok/skills/parent/SKILL.md +3 -9
- package/.grok/skills/solo/SKILL.md +2 -0
- package/.grok/skills/write-plan/SKILL.md +4 -8
- package/.rulesync/hooks.jsonc +2 -1
- package/.rulesync/reference/cascade.md +2 -2
- package/.rulesync/reference/hooks.md +2 -2
- package/.rulesync/reference/map.md +3 -3
- package/.rulesync/rules/CLAUDE.md +7 -3
- package/.rulesync/rules/overview.md +7 -3
- package/.rulesync/skills/cascade/SKILL.md +26 -0
- package/.rulesync/skills/explain/SKILL.md +5 -3
- package/.rulesync/skills/handoff/SKILL.md +3 -2
- package/.rulesync/skills/parent/SKILL.md +3 -9
- package/.rulesync/skills/solo/SKILL.md +2 -0
- package/.rulesync/subagents/orchestrator.md +11 -8
- package/AGENTS.md +7 -3
- package/CLAUDE.md +7 -3
- package/README.md +4 -2
- package/bin/grunt.js +3 -1
- package/cli/grunt.mjs +148 -15
- package/cli/grunt.test.ts +304 -25
- package/cli/init.mjs +65 -43
- package/cli/init.test.ts +78 -0
- package/cli/prompt.mjs +36 -0
- package/cli/prompt.test.ts +199 -0
- package/package.json +4 -1
package/cli/prompt.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as clack from "@clack/prompts"
|
|
2
|
+
|
|
3
|
+
export function isInteractive({
|
|
4
|
+
argv = process.argv.slice(2),
|
|
5
|
+
env = process.env,
|
|
6
|
+
stdin = process.stdin,
|
|
7
|
+
stdout = process.stdout,
|
|
8
|
+
} = {}) {
|
|
9
|
+
if (!stdin?.isTTY || !stdout?.isTTY) return false
|
|
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
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const clackSelect = vi.hoisted(() => vi.fn());
|
|
4
|
+
const clackConfirm = vi.hoisted(() => vi.fn());
|
|
5
|
+
const clackSpinner = vi.hoisted(() => vi.fn());
|
|
6
|
+
const clackIsCancel = vi.hoisted(() => vi.fn());
|
|
7
|
+
const clackCancel = vi.hoisted(() => vi.fn());
|
|
8
|
+
|
|
9
|
+
vi.mock("@clack/prompts", () => ({
|
|
10
|
+
select: clackSelect,
|
|
11
|
+
confirm: clackConfirm,
|
|
12
|
+
spinner: clackSpinner,
|
|
13
|
+
isCancel: clackIsCancel,
|
|
14
|
+
cancel: clackCancel,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
bailIfCancel,
|
|
19
|
+
confirm,
|
|
20
|
+
isInteractive,
|
|
21
|
+
select,
|
|
22
|
+
spinner,
|
|
23
|
+
} from "./prompt.mjs";
|
|
24
|
+
|
|
25
|
+
function tty(isTTY: boolean) {
|
|
26
|
+
return { isTTY } as NodeJS.ReadStream & NodeJS.WriteStream;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("isInteractive", () => {
|
|
30
|
+
it("true when both TTY, CI unset, no yes flags", () => {
|
|
31
|
+
expect(
|
|
32
|
+
isInteractive({
|
|
33
|
+
argv: [],
|
|
34
|
+
env: {},
|
|
35
|
+
stdin: tty(true),
|
|
36
|
+
stdout: tty(true),
|
|
37
|
+
}),
|
|
38
|
+
).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("true when CI is 0 or false or empty", () => {
|
|
42
|
+
for (const CI of ["0", "false", ""]) {
|
|
43
|
+
expect(
|
|
44
|
+
isInteractive({
|
|
45
|
+
argv: [],
|
|
46
|
+
env: { CI },
|
|
47
|
+
stdin: tty(true),
|
|
48
|
+
stdout: tty(true),
|
|
49
|
+
}),
|
|
50
|
+
).toBe(true);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("false when stdin not TTY", () => {
|
|
55
|
+
expect(
|
|
56
|
+
isInteractive({
|
|
57
|
+
argv: [],
|
|
58
|
+
env: {},
|
|
59
|
+
stdin: tty(false),
|
|
60
|
+
stdout: tty(true),
|
|
61
|
+
}),
|
|
62
|
+
).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("false when stdout not TTY", () => {
|
|
66
|
+
expect(
|
|
67
|
+
isInteractive({
|
|
68
|
+
argv: [],
|
|
69
|
+
env: {},
|
|
70
|
+
stdin: tty(true),
|
|
71
|
+
stdout: tty(false),
|
|
72
|
+
}),
|
|
73
|
+
).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("false when stdin isTTY missing", () => {
|
|
77
|
+
expect(
|
|
78
|
+
isInteractive({
|
|
79
|
+
argv: [],
|
|
80
|
+
env: {},
|
|
81
|
+
stdin: {} as NodeJS.ReadStream,
|
|
82
|
+
stdout: tty(true),
|
|
83
|
+
}),
|
|
84
|
+
).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it.each(["1", "true", "yes", "CI"])("false when CI=%s", (CI) => {
|
|
88
|
+
expect(
|
|
89
|
+
isInteractive({
|
|
90
|
+
argv: [],
|
|
91
|
+
env: { CI },
|
|
92
|
+
stdin: tty(true),
|
|
93
|
+
stdout: tty(true),
|
|
94
|
+
}),
|
|
95
|
+
).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it.each(["--yes", "-y", "--non-interactive"])("false when argv has %s", (flag) => {
|
|
99
|
+
expect(
|
|
100
|
+
isInteractive({
|
|
101
|
+
argv: [flag],
|
|
102
|
+
env: {},
|
|
103
|
+
stdin: tty(true),
|
|
104
|
+
stdout: tty(true),
|
|
105
|
+
}),
|
|
106
|
+
).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("uses process defaults", () => {
|
|
110
|
+
const stdinDesc = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
|
|
111
|
+
const stdoutDesc = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
|
|
112
|
+
const argv = process.argv;
|
|
113
|
+
const ci = process.env.CI;
|
|
114
|
+
Object.defineProperty(process.stdin, "isTTY", { configurable: true, value: true });
|
|
115
|
+
Object.defineProperty(process.stdout, "isTTY", { configurable: true, value: true });
|
|
116
|
+
process.argv = ["node", "grunt"];
|
|
117
|
+
delete process.env.CI;
|
|
118
|
+
try {
|
|
119
|
+
expect(isInteractive()).toBe(true);
|
|
120
|
+
process.argv = ["node", "grunt", "--yes"];
|
|
121
|
+
expect(isInteractive()).toBe(false);
|
|
122
|
+
} finally {
|
|
123
|
+
process.argv = argv;
|
|
124
|
+
if (ci === undefined) delete process.env.CI;
|
|
125
|
+
else process.env.CI = ci;
|
|
126
|
+
if (stdinDesc) Object.defineProperty(process.stdin, "isTTY", stdinDesc);
|
|
127
|
+
else delete (process.stdin as { isTTY?: boolean }).isTTY;
|
|
128
|
+
if (stdoutDesc) Object.defineProperty(process.stdout, "isTTY", stdoutDesc);
|
|
129
|
+
else delete (process.stdout as { isTTY?: boolean }).isTTY;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("bailIfCancel", () => {
|
|
135
|
+
afterEach(() => {
|
|
136
|
+
clackIsCancel.mockReset();
|
|
137
|
+
clackCancel.mockReset();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("returns value when not cancel", () => {
|
|
141
|
+
clackIsCancel.mockReturnValue(false);
|
|
142
|
+
expect(bailIfCancel("ok")).toBe("ok");
|
|
143
|
+
expect(clackCancel).not.toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("cancel Aborted and exit 0", () => {
|
|
147
|
+
clackIsCancel.mockReturnValue(true);
|
|
148
|
+
const exit = vi.spyOn(process, "exit").mockImplementation((() => {
|
|
149
|
+
throw new Error("EXIT");
|
|
150
|
+
}) as typeof process.exit);
|
|
151
|
+
expect(() => bailIfCancel(Symbol("cancel"))).toThrow("EXIT");
|
|
152
|
+
expect(clackCancel).toHaveBeenCalledWith("Aborted");
|
|
153
|
+
expect(exit).toHaveBeenCalledWith(0);
|
|
154
|
+
exit.mockRestore();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("clack wrappers", () => {
|
|
159
|
+
afterEach(() => {
|
|
160
|
+
clackSelect.mockReset();
|
|
161
|
+
clackConfirm.mockReset();
|
|
162
|
+
clackSpinner.mockReset();
|
|
163
|
+
clackIsCancel.mockReset();
|
|
164
|
+
clackCancel.mockReset();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("select bails then returns", async () => {
|
|
168
|
+
clackSelect.mockResolvedValue("init");
|
|
169
|
+
clackIsCancel.mockReturnValue(false);
|
|
170
|
+
await expect(select({ message: "Command" })).resolves.toBe("init");
|
|
171
|
+
expect(clackSelect).toHaveBeenCalledWith({ message: "Command" });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("confirm bails then returns", async () => {
|
|
175
|
+
clackConfirm.mockResolvedValue(true);
|
|
176
|
+
clackIsCancel.mockReturnValue(false);
|
|
177
|
+
await expect(confirm({ message: "Re-init?" })).resolves.toBe(true);
|
|
178
|
+
expect(clackConfirm).toHaveBeenCalledWith({ message: "Re-init?" });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("select cancel exits 0", async () => {
|
|
182
|
+
clackSelect.mockResolvedValue("x");
|
|
183
|
+
clackIsCancel.mockReturnValue(true);
|
|
184
|
+
const exit = vi.spyOn(process, "exit").mockImplementation((() => {
|
|
185
|
+
throw new Error("EXIT");
|
|
186
|
+
}) as typeof process.exit);
|
|
187
|
+
await expect(select({})).rejects.toThrow("EXIT");
|
|
188
|
+
expect(clackCancel).toHaveBeenCalledWith("Aborted");
|
|
189
|
+
expect(exit).toHaveBeenCalledWith(0);
|
|
190
|
+
exit.mockRestore();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("spinner passthrough", () => {
|
|
194
|
+
const s = { start: vi.fn(), stop: vi.fn() };
|
|
195
|
+
clackSpinner.mockReturnValue(s);
|
|
196
|
+
expect(spinner()).toBe(s);
|
|
197
|
+
expect(clackSpinner).toHaveBeenCalledOnce();
|
|
198
|
+
});
|
|
199
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovrozagar/grunt",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
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",
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@clack/prompts": "^0.11.0"
|
|
60
|
+
},
|
|
58
61
|
"scripts": {
|
|
59
62
|
"rulesync:generate": "rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills && rulesync generate -t claudecode,codexcli,antigravity-cli -f hooks && node scripts/emit-mcp-policy.mjs && node scripts/emit-gemini.mjs && node scripts/emit-agent-shell-tools.mjs && node scripts/hooks-union.mjs",
|
|
60
63
|
"rulesync:watch": "node scripts/emit-mcp-policy.mjs && node scripts/emit-gemini.mjs && node scripts/emit-agent-shell-tools.mjs && rulesync generate -t claudecode,codexcli,antigravity-cli,grokcli -f rules,subagents,skills --watch",
|