@lovrozagar/grunt 0.6.1 → 0.6.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/CHANGELOG.md +11 -0
- package/README.md +2 -2
- package/cli/grunt.mjs +3 -4
- package/cli/grunt.test.ts +3 -2
- package/package.json +1 -1
- package/scripts/check-globals.mjs +5 -2
- package/scripts/emit-mcp-policy.mjs +13 -7
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.2] - 2026-09-17
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- TTY init defaults `Apply global prompt optimizations? (recommended)` to Yes (`--skip-globals` still defaults No)
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Init in `$HOME` no longer fails `emit-mcp-policy --check` after globals write `[features]` into the same `~/.grok/config.toml`
|
|
19
|
+
- TTY init spinner `stop` keeps the phase name (no empty `◇`)
|
|
20
|
+
|
|
10
21
|
## [0.6.1] - 2026-09-17
|
|
11
22
|
|
|
12
23
|
### Changed
|
package/README.md
CHANGED
|
@@ -95,8 +95,8 @@ Rulesync schema doctor is separate: `npm run grunt:rulesync:doctor`.
|
|
|
95
95
|
|
|
96
96
|
- Merge SoT `npm install` `grunt:rulesync:generate` `grunt:sync:globals:apply` `grunt:rulesync:check`
|
|
97
97
|
- `--skip-globals` skips apply
|
|
98
|
-
- TTY init asks `Apply global prompt optimizations? (recommended)`
|
|
99
|
-
-
|
|
98
|
+
- TTY init asks `Apply global prompt optimizations? (recommended)` (default Yes; `--skip-globals` defaults No)
|
|
99
|
+
- Non-interactive re-init auto-skips globals when `<!-- grunt:begin -->` in `AGENTS.md`/`CLAUDE.md`
|
|
100
100
|
- First init (no sentinel) applies globals unless flagged
|
|
101
101
|
- Owned trees/scripts refresh; extra `.rulesync` files kept; patches to grunt-owned files lost
|
|
102
102
|
- `cp` cannot delete dest extras. Init/upgrade then prune: retired skills `parent` `solo` `cascade`, agents `implementer` `thinker` (plus `.grok/roles/{implementer,thinker}.toml` and `.gemini/agents/{name}/`), scripts `telemetry.mjs` `grunt-config.mjs`, paths `.grok/parent.md` `.grok/skills/shared` `.rulesync/grunt.config.jsonc` plus local overlay and example, and reserved skill dirs this package no longer ships. Consumer extras stay.
|
package/cli/grunt.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process"
|
|
|
2
2
|
import { readFileSync } from "node:fs"
|
|
3
3
|
import path from "node:path"
|
|
4
4
|
import { fileURLToPath } from "node:url"
|
|
5
|
-
import { destAlreadyInited, init, RESERVED_SKILLS,
|
|
5
|
+
import { destAlreadyInited, init, RESERVED_SKILLS, toGruntScriptName } from "./init.mjs"
|
|
6
6
|
import { confirm, isInteractive, select, spinner } from "./prompt.mjs"
|
|
7
7
|
|
|
8
8
|
const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
@@ -105,7 +105,7 @@ function bindSpinner() {
|
|
|
105
105
|
const spin = spinner()
|
|
106
106
|
return (name, action) => {
|
|
107
107
|
if (action === "start") spin.start(name)
|
|
108
|
-
else if (action === "stop") spin.stop()
|
|
108
|
+
else if (action === "stop") spin.stop(name)
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -123,10 +123,9 @@ async function runInit(cwd, { skipGlobals, interactive }) {
|
|
|
123
123
|
})
|
|
124
124
|
if (!again) return
|
|
125
125
|
}
|
|
126
|
-
const autoSkip = skipGlobals || shouldAutoSkipGlobals(cwd)
|
|
127
126
|
const applyGlobals = await confirm({
|
|
128
127
|
message: APPLY_GLOBALS_CONFIRM,
|
|
129
|
-
initialValue: !
|
|
128
|
+
initialValue: !skipGlobals,
|
|
130
129
|
})
|
|
131
130
|
init(cwd, {
|
|
132
131
|
skipGlobals: !applyGlobals,
|
package/cli/grunt.test.ts
CHANGED
|
@@ -401,7 +401,7 @@ describe("start", () => {
|
|
|
401
401
|
);
|
|
402
402
|
expect(globalsConfirm?.[0]).toMatchObject({
|
|
403
403
|
message: APPLY_GLOBALS_CONFIRM,
|
|
404
|
-
initialValue:
|
|
404
|
+
initialValue: true,
|
|
405
405
|
});
|
|
406
406
|
expect(init).toHaveBeenCalledWith(
|
|
407
407
|
process.cwd(),
|
|
@@ -413,8 +413,9 @@ describe("start", () => {
|
|
|
413
413
|
);
|
|
414
414
|
expect(isInteractive).toHaveBeenCalledOnce();
|
|
415
415
|
expect(spin.start).toHaveBeenCalledWith("merge");
|
|
416
|
-
expect(spin.stop).
|
|
416
|
+
expect(spin.stop).toHaveBeenCalledWith("merge");
|
|
417
417
|
expect(spin.start).toHaveBeenCalledWith("install");
|
|
418
|
+
expect(spin.stop).toHaveBeenCalledWith("install");
|
|
418
419
|
});
|
|
419
420
|
|
|
420
421
|
it("TTY init --skip-globals default confirm false maps to skip", async () => {
|
package/package.json
CHANGED
|
@@ -36,7 +36,7 @@ export function checkGlobals({ home, workspaceRoot } = {}) {
|
|
|
36
36
|
if (!homeDir) {
|
|
37
37
|
return { ok: false, error: `HOME required; ${APPLY_HINT}` };
|
|
38
38
|
}
|
|
39
|
-
const homeConfig = path.
|
|
39
|
+
const homeConfig = path.resolve(homeDir, HOME_REL);
|
|
40
40
|
const homeParsed = parseTomlFile(homeConfig, "home ~/.grok/config.toml");
|
|
41
41
|
if (!homeParsed.ok) {
|
|
42
42
|
if (homeParsed.missing) {
|
|
@@ -54,7 +54,10 @@ export function checkGlobals({ home, workspaceRoot } = {}) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
const projectConfig = path.
|
|
57
|
+
const projectConfig = path.resolve(ws, PROJECT_REL);
|
|
58
|
+
if (projectConfig === homeConfig) {
|
|
59
|
+
return { ok: true };
|
|
60
|
+
}
|
|
58
61
|
const projectParsed = parseTomlFile(projectConfig, "project .grok/config.toml");
|
|
59
62
|
if (!projectParsed.ok) {
|
|
60
63
|
if (projectParsed.missing) {
|
|
@@ -175,9 +175,14 @@ function isMcpToolRule(s) {
|
|
|
175
175
|
return s === "MCPTool" || s.startsWith("MCPTool(");
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
function
|
|
178
|
+
export function isHomeGrokConfig(workspaceRoot, home) {
|
|
179
|
+
if (!workspaceRoot || !home) return false;
|
|
180
|
+
return path.resolve(workspaceRoot, GROK_REL) === path.resolve(home, GROK_REL);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function desiredGrok(disk, policy, { keepHomeTables = false } = {}) {
|
|
179
184
|
const obj = isPlainObject(disk) ? { ...disk } : {};
|
|
180
|
-
delete obj.features;
|
|
185
|
+
if (!keepHomeTables) delete obj.features;
|
|
181
186
|
delete obj.permissions;
|
|
182
187
|
|
|
183
188
|
const plugins = isPlainObject(obj.plugins) ? { ...obj.plugins } : {};
|
|
@@ -288,9 +293,9 @@ function geminiCheckOk(disk, desired) {
|
|
|
288
293
|
return deepEqual(disk.mcpServers, desired.mcpServers);
|
|
289
294
|
}
|
|
290
295
|
|
|
291
|
-
function grokCheckOk(disk, desired, policy) {
|
|
296
|
+
function grokCheckOk(disk, desired, policy, { keepHomeTables = false } = {}) {
|
|
292
297
|
if (!isPlainObject(disk)) return false;
|
|
293
|
-
if (Object.prototype.hasOwnProperty.call(disk, "features")) return false;
|
|
298
|
+
if (!keepHomeTables && Object.prototype.hasOwnProperty.call(disk, "features")) return false;
|
|
294
299
|
if (Array.isArray(disk.permissions)) return false;
|
|
295
300
|
if (isPlainObject(disk.plugins) && Object.prototype.hasOwnProperty.call(disk.plugins, "deny_default")) {
|
|
296
301
|
return false;
|
|
@@ -336,13 +341,14 @@ function writeOrCheck({ check, abs, nextText, diskRaw }) {
|
|
|
336
341
|
return true;
|
|
337
342
|
}
|
|
338
343
|
|
|
339
|
-
export function emitMcpPolicy({ workspaceRoot, check = false } = {}) {
|
|
344
|
+
export function emitMcpPolicy({ workspaceRoot, check = false, home } = {}) {
|
|
340
345
|
const ws = workspaceRoot || process.cwd();
|
|
341
346
|
const policy = loadPolicy(ws);
|
|
342
347
|
const valid = validatePolicy(policy);
|
|
343
348
|
if (!valid.ok) {
|
|
344
349
|
return { ok: false, check, error: valid.error };
|
|
345
350
|
}
|
|
351
|
+
const keepHomeTables = isHomeGrokConfig(ws, home || process.env.HOME);
|
|
346
352
|
|
|
347
353
|
const grokAbs = path.join(ws, GROK_REL);
|
|
348
354
|
const claudeAbs = path.join(ws, CLAUDE_SETTINGS_REL);
|
|
@@ -365,7 +371,7 @@ export function emitMcpPolicy({ workspaceRoot, check = false } = {}) {
|
|
|
365
371
|
const agentsDisk = agentsRaw == null ? null : parseJsonFile(agentsRaw, {});
|
|
366
372
|
const geminiDisk = geminiRaw == null ? null : parseJsonFile(geminiRaw, {});
|
|
367
373
|
|
|
368
|
-
const grokDesired = desiredGrok(grokDisk, policy);
|
|
374
|
+
const grokDesired = desiredGrok(grokDisk, policy, { keepHomeTables });
|
|
369
375
|
const claudeDesired = desiredClaudeSettings(claudeDisk, policy);
|
|
370
376
|
const mcpDesired = desiredMcpJson(policy);
|
|
371
377
|
const codexDesired = desiredCodex(codexDisk, policy);
|
|
@@ -374,7 +380,7 @@ export function emitMcpPolicy({ workspaceRoot, check = false } = {}) {
|
|
|
374
380
|
|
|
375
381
|
if (check) {
|
|
376
382
|
const drift = [];
|
|
377
|
-
if (grokRaw == null || !grokCheckOk(grokDisk, grokDesired, policy)) drift.push(GROK_REL);
|
|
383
|
+
if (grokRaw == null || !grokCheckOk(grokDisk, grokDesired, policy, { keepHomeTables })) drift.push(GROK_REL);
|
|
378
384
|
if (claudeRaw == null || !claudeCheckOk(claudeDisk, claudeDesired)) drift.push(CLAUDE_SETTINGS_REL);
|
|
379
385
|
if (mcpDisk == null || !deepEqual(mcpDisk, mcpDesired)) drift.push(MCP_JSON_REL);
|
|
380
386
|
const codexOk =
|