@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 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)` before writing `~/.grok/config.toml`
99
- - Re-init auto-skips globals when `<!-- grunt:begin -->` in `AGENTS.md`/`CLAUDE.md`
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, shouldAutoSkipGlobals, toGruntScriptName } from "./init.mjs"
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: !autoSkip,
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: false,
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).toHaveBeenCalled();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovrozagar/grunt",
3
- "version": "0.6.1",
3
+ "version": "0.6.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",
@@ -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.join(homeDir, HOME_REL);
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.join(ws, PROJECT_REL);
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 desiredGrok(disk, policy) {
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 =