@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.
Files changed (157) hide show
  1. package/.agents/agents/grunt.md +2 -24
  2. package/.agents/agents/orchestrator.md +5 -50
  3. package/.agents/skills/ask/SKILL.md +5 -12
  4. package/.agents/skills/auto/SKILL.md +7 -12
  5. package/.agents/skills/browser/SKILL.md +4 -8
  6. package/.agents/skills/clasp/SKILL.md +11 -0
  7. package/.agents/skills/commit/SKILL.md +3 -1
  8. package/.agents/skills/commit-push/SKILL.md +2 -0
  9. package/.agents/skills/commit-push-deploy/SKILL.md +2 -0
  10. package/.agents/skills/commit-push-release/SKILL.md +2 -0
  11. package/.agents/skills/explain/SKILL.md +7 -13
  12. package/.agents/skills/google-workspace/SKILL.md +13 -0
  13. package/.agents/skills/handoff/SKILL.md +7 -11
  14. package/.agents/skills/implement-plan/SKILL.md +5 -109
  15. package/.agents/skills/listen/SKILL.md +15 -0
  16. package/.agents/skills/pickup/SKILL.md +6 -12
  17. package/.agents/skills/speak/SKILL.md +13 -0
  18. package/.agents/skills/tmp/SKILL.md +6 -10
  19. package/.agents/skills/write-plan/SKILL.md +5 -77
  20. package/.claude/agents/grunt.md +2 -26
  21. package/.claude/agents/orchestrator.md +12 -50
  22. package/.claude/skills/ask/SKILL.md +5 -12
  23. package/.claude/skills/auto/SKILL.md +7 -12
  24. package/.claude/skills/browser/SKILL.md +4 -8
  25. package/.claude/skills/clasp/SKILL.md +11 -0
  26. package/.claude/skills/commit/SKILL.md +3 -1
  27. package/.claude/skills/commit-push/SKILL.md +2 -0
  28. package/.claude/skills/commit-push-deploy/SKILL.md +2 -0
  29. package/.claude/skills/commit-push-release/SKILL.md +2 -0
  30. package/.claude/skills/explain/SKILL.md +7 -13
  31. package/.claude/skills/google-workspace/SKILL.md +13 -0
  32. package/.claude/skills/handoff/SKILL.md +7 -11
  33. package/.claude/skills/implement-plan/SKILL.md +5 -109
  34. package/.claude/skills/listen/SKILL.md +15 -0
  35. package/.claude/skills/pickup/SKILL.md +6 -12
  36. package/.claude/skills/speak/SKILL.md +13 -0
  37. package/.claude/skills/tmp/SKILL.md +6 -10
  38. package/.claude/skills/write-plan/SKILL.md +5 -77
  39. package/.codex/agents/grunt.toml +2 -26
  40. package/.codex/agents/orchestrator.toml +4 -50
  41. package/.grok/agents/grunt.md +2 -24
  42. package/.grok/agents/orchestrator.md +4 -48
  43. package/.grok/global-settings.toml +1 -3
  44. package/.grok/hooks/orchestrate-parent.js +190 -358
  45. package/.grok/skills/ask/SKILL.md +5 -12
  46. package/.grok/skills/auto/SKILL.md +7 -12
  47. package/.grok/skills/browser/SKILL.md +4 -8
  48. package/.grok/skills/clasp/SKILL.md +11 -0
  49. package/.grok/skills/commit/SKILL.md +3 -1
  50. package/.grok/skills/commit-push/SKILL.md +2 -0
  51. package/.grok/skills/commit-push-deploy/SKILL.md +2 -0
  52. package/.grok/skills/commit-push-release/SKILL.md +2 -0
  53. package/.grok/skills/explain/SKILL.md +7 -13
  54. package/.grok/skills/google-workspace/SKILL.md +13 -0
  55. package/.grok/skills/handoff/SKILL.md +7 -11
  56. package/.grok/skills/implement-plan/SKILL.md +5 -109
  57. package/.grok/skills/listen/SKILL.md +15 -0
  58. package/.grok/skills/pickup/SKILL.md +6 -12
  59. package/.grok/skills/speak/SKILL.md +13 -0
  60. package/.grok/skills/tmp/SKILL.md +6 -10
  61. package/.grok/skills/write-plan/SKILL.md +5 -77
  62. package/.rulesync/hooks.jsonc +1 -1
  63. package/.rulesync/mcp-policy.jsonc +0 -2
  64. package/.rulesync/reference/INDEX.md +30 -20
  65. package/.rulesync/reference/browser.md +13 -6
  66. package/.rulesync/reference/cascade.md +6 -86
  67. package/.rulesync/reference/clasp.md +21 -0
  68. package/.rulesync/reference/google-workspace.md +79 -0
  69. package/.rulesync/reference/hooks.md +7 -11
  70. package/.rulesync/reference/implementation-format.md +113 -0
  71. package/.rulesync/reference/law.md +5 -3
  72. package/.rulesync/reference/listen.md +80 -0
  73. package/.rulesync/reference/map.md +40 -36
  74. package/.rulesync/reference/output.md +3 -51
  75. package/.rulesync/reference/plan-format.md +4 -2
  76. package/.rulesync/reference/refs-map.md +12 -5
  77. package/.rulesync/reference/rtk.md +3 -2
  78. package/.rulesync/reference/scope.md +37 -0
  79. package/.rulesync/reference/setup.md +27 -0
  80. package/.rulesync/reference/skills-map.md +13 -12
  81. package/.rulesync/reference/speak.md +59 -0
  82. package/.rulesync/rules/CLAUDE.md +3 -49
  83. package/.rulesync/rules/overview.md +9 -47
  84. package/.rulesync/skills/ask/SKILL.md +5 -12
  85. package/.rulesync/skills/auto/SKILL.md +5 -12
  86. package/.rulesync/skills/browser/SKILL.md +4 -8
  87. package/.rulesync/skills/clasp/SKILL.md +9 -0
  88. package/.rulesync/skills/commit/SKILL.md +3 -1
  89. package/.rulesync/skills/commit-push/SKILL.md +2 -0
  90. package/.rulesync/skills/commit-push-deploy/SKILL.md +2 -0
  91. package/.rulesync/skills/commit-push-release/SKILL.md +2 -0
  92. package/.rulesync/skills/explain/SKILL.md +7 -14
  93. package/.rulesync/skills/google-workspace/SKILL.md +11 -0
  94. package/.rulesync/skills/handoff/SKILL.md +7 -11
  95. package/.rulesync/skills/implement-plan/SKILL.md +5 -119
  96. package/.rulesync/skills/listen/SKILL.md +13 -0
  97. package/.rulesync/skills/pickup/SKILL.md +6 -12
  98. package/.rulesync/skills/speak/SKILL.md +13 -0
  99. package/.rulesync/skills/tmp/SKILL.md +6 -10
  100. package/.rulesync/skills/write-plan/SKILL.md +5 -84
  101. package/.rulesync/subagents/grunt.md +2 -24
  102. package/.rulesync/subagents/orchestrator.md +14 -59
  103. package/AGENTS.md +9 -47
  104. package/CHANGELOG.md +67 -0
  105. package/CLAUDE.md +1 -49
  106. package/README.md +89 -80
  107. package/cli/grunt.mjs +21 -3
  108. package/cli/grunt.test.ts +47 -3
  109. package/cli/init.mjs +134 -234
  110. package/cli/init.test.ts +241 -58
  111. package/cli/prompt.mjs +9 -36
  112. package/cli/prompt.test.ts +14 -0
  113. package/package.json +10 -2
  114. package/scripts/browser.mjs +128 -7
  115. package/scripts/doctor.mjs +104 -10
  116. package/scripts/emit-gemini.mjs +2 -2
  117. package/scripts/emit-mcp-policy.mjs +1 -1
  118. package/scripts/gate-fat-tools.mjs +223 -173
  119. package/scripts/google-workspace.mjs +713 -0
  120. package/scripts/grunt-job.mjs +453 -69
  121. package/scripts/guarded-md.mjs +230 -0
  122. package/scripts/guarded-roots.mjs +1 -1
  123. package/scripts/interactive.mjs +15 -0
  124. package/scripts/listen.mjs +829 -0
  125. package/scripts/parse-need.mjs +21 -2
  126. package/scripts/persist-implementation.mjs +278 -0
  127. package/scripts/prompt.mjs +32 -0
  128. package/scripts/scrub-spawn-prompt.mjs +3 -50
  129. package/scripts/setup.mjs +746 -0
  130. package/scripts/speak.mjs +291 -0
  131. package/.agents/agents/implementer.md +0 -32
  132. package/.agents/agents/thinker.md +0 -29
  133. package/.agents/skills/cascade/SKILL.md +0 -28
  134. package/.agents/skills/parent/SKILL.md +0 -6
  135. package/.agents/skills/solo/SKILL.md +0 -25
  136. package/.claude/agents/implementer.md +0 -37
  137. package/.claude/agents/thinker.md +0 -36
  138. package/.claude/skills/cascade/SKILL.md +0 -28
  139. package/.claude/skills/parent/SKILL.md +0 -6
  140. package/.claude/skills/solo/SKILL.md +0 -25
  141. package/.codex/agents/implementer.toml +0 -26
  142. package/.codex/agents/thinker.toml +0 -26
  143. package/.grok/agents/implementer.md +0 -27
  144. package/.grok/agents/thinker.md +0 -27
  145. package/.grok/parent.md +0 -1
  146. package/.grok/roles/implementer.toml +0 -5
  147. package/.grok/roles/thinker.toml +0 -5
  148. package/.grok/skills/cascade/SKILL.md +0 -28
  149. package/.grok/skills/parent/SKILL.md +0 -6
  150. package/.grok/skills/solo/SKILL.md +0 -25
  151. package/.rulesync/grunt.config.jsonc +0 -7
  152. package/.rulesync/skills/cascade/SKILL.md +0 -28
  153. package/.rulesync/skills/parent/SKILL.md +0 -6
  154. package/.rulesync/skills/solo/SKILL.md +0 -25
  155. package/.rulesync/subagents/implementer.md +0 -49
  156. package/.rulesync/subagents/thinker.md +0 -49
  157. package/scripts/grunt-config.mjs +0 -50
package/README.md CHANGED
@@ -27,23 +27,23 @@ OSS drop-in; merges existing configs; switches default provider flow to Grunt.
27
27
 
28
28
  ```
29
29
  npm i -D @lovrozagar/grunt
30
- npx @lovrozagar/grunt
30
+ npm exec grunt
31
31
  ```
32
32
 
33
- - Same as `npx @lovrozagar/grunt init` when no command
33
+ - Same as `npm exec grunt init` when no command
34
34
  - Do not `npm test` as a consumer
35
- - Package: `@lovrozagar/grunt` `0.4.2` MIT · https://github.com/lovrozagar/grunt
35
+ - Package: `@lovrozagar/grunt` `0.6.0` MIT · https://github.com/lovrozagar/grunt
36
36
 
37
37
  ## Prerequisites
38
38
 
39
39
  All OS. Print-only. Never auto-install.
40
40
 
41
41
  ```
42
- npx grunt doctor
42
+ npm exec grunt doctor
43
43
  node scripts/doctor.mjs
44
44
  ```
45
45
 
46
- Exit 1 if any required missing; 0 if all required ok. Optional `gh` reported only.
46
+ Exit 1 if any required missing; 0 if all required ok. Optional tools are reported; missing optional does not fail.
47
47
 
48
48
  | tool | required | install |
49
49
  | --- | --- | --- |
@@ -54,12 +54,18 @@ Exit 1 if any required missing; 0 if all required ok. Optional `gh` reported onl
54
54
  | lightpanda | yes | `curl -fsSL https://pkg.lightpanda.io/install.sh \| bash` · mac `brew tap lightpanda-io/browser` · win WSL only |
55
55
  | chromium-family | yes | linux `sudo apt install chromium` · mac `brew install --cask chromium` or `google-chrome` · win `winget install Google.Chrome` / `Microsoft.Edge` + PATH |
56
56
  | gh | no | report only |
57
+ | clasp | no | `npm i -g @google/clasp` · custom Apps Script only |
58
+ | google-workspace | no | `node scripts/google-workspace.mjs` · Sheets Docs Slides Calendar Gmail · doctor reports `oauth`/`tokens`/`adc`/`clasprc` (no secrets) · `grunt setup` / `node scripts/setup.mjs google-workspace` |
59
+ | speak | no | `node scripts/speak.mjs` · TTS output (ElevenLabs or OpenAI) · `grunt setup` / `node scripts/setup.mjs speak` · or `ELEVENLABS_API_KEY` / `OPENAI_API_KEY` / `~/.grunt/speak.json` |
60
+ | ffmpeg | no | mic capture for `/listen` · mac `brew install ffmpeg` · linux `sudo apt install ffmpeg` · win `winget install Gyan.FFmpeg` |
61
+ | whisper-cli | no | local STT for `/listen` · mac `brew install whisper-cpp` · first listen downloads `ggml-base.en.bin` · setup in `.rulesync/reference/listen.md` |
62
+ | listen | no | `node scripts/listen.mjs` · STT input · `grunt setup` / `node scripts/setup.mjs listen` · local whisper.cpp then OpenAI Whisper |
57
63
 
58
64
  Rulesync schema doctor is separate: `npm run grunt:rulesync:doctor`.
59
65
 
60
66
  ## Usage
61
67
 
62
- - TTY no command → menu (init default; generate check sync-globals purge-mcps doctor help quit)
68
+ - TTY no command → menu (init default; generate check sync-globals purge-mcps doctor setup upgrade help quit)
63
69
  - Piped / CI / `--yes` / `-y` / `--non-interactive` no command → still `init`
64
70
  - Bin: `grunt` → `./bin/grunt.js` (`type: module`)
65
71
 
@@ -72,7 +78,9 @@ Rulesync schema doctor is separate: `npm run grunt:rulesync:doctor`.
72
78
  - `check` → `npm run grunt:rulesync:check`
73
79
  - `sync-globals` → `npm run grunt:sync:globals` (dry-run); `--apply` → `grunt:sync:globals:apply`
74
80
  - `purge-mcps` → `npm run grunt:purge:global-mcps` (dry-run); `--apply` → `grunt:purge:global-mcps:apply`
75
- - `doctor` → `npm run grunt:doctor` (`npx grunt doctor` / `node scripts/doctor.mjs` stay). Rulesync schema: `npm run grunt:rulesync:doctor`
81
+ - `doctor` → `npm run grunt:doctor` (`npm exec grunt doctor` / `node scripts/doctor.mjs` stay). Rulesync schema: `npm run grunt:rulesync:doctor`
82
+ - `setup` → `npm run grunt:setup` (`node scripts/setup.mjs`) — handheld speak / listen / google-workspace / browser. TTY walks each; flags for non-TTY keys/`--creds`
83
+ - `upgrade` → same merge as init for an already-inited repo: copy owned trees/scripts, prune retired grunt-owned names, print reserved skill names
76
84
  - `help`
77
85
  - `version`
78
86
 
@@ -87,35 +95,38 @@ Rulesync schema doctor is separate: `npm run grunt:rulesync:doctor`.
87
95
 
88
96
  - Merge SoT `npm install` `grunt:rulesync:generate` `grunt:sync:globals:apply` `grunt:rulesync:check`
89
97
  - `--skip-globals` skips apply
98
+ - TTY init asks `Apply global prompt optimizations? (recommended)` before writing `~/.grok/config.toml`
90
99
  - Re-init auto-skips globals when `<!-- grunt:begin -->` in `AGENTS.md`/`CLAUDE.md`
91
100
  - First init (no sentinel) applies globals unless flagged
92
101
  - Owned trees/scripts refresh; extra `.rulesync` files kept; patches to grunt-owned files lost
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.
93
103
  - Breaking: consumer npm scripts are `grunt:<SoT-key>` (`grunt:rulesync:generate`, `grunt:doctor`). Re-init migrates `package.json` (owned unprefixed keys + suffixes; `npm run` refs in other dest scripts). CI/husky/`npm run rulesync:*` / `npm run doctor` must switch. No aliases. SoT repo scripts stay unprefixed (`npm run rulesync:generate`).
94
104
 
95
105
  ## Version bump
96
106
 
107
+ Already-inited consumer (0.5.x → 0.6, and later):
108
+
97
109
  ```
98
- npm i -D @lovrozagar/grunt@latest && npx @lovrozagar/grunt init
110
+ npm i -D @lovrozagar/grunt@latest
111
+ npm exec grunt upgrade
99
112
  ```
100
113
 
101
- - Same re-init / globals rules as Init
114
+ `upgrade` is init plus a reserved-names print. Same merge, prune, and globals-skip rules as Init. New skills/scripts appear because they are in the package copy list. Dropped grunt-owned files disappear only if they are on the retired lists (or a reserved skill this package no longer ships). Do not expect a blind dest-dir mirror-delete; that would wipe consumer extras.
102
115
 
103
116
  ## Agents
104
117
 
105
- SoT: `.rulesync/subagents/{orchestrator grunt implementer thinker}.md`
118
+ SoT: `.rulesync/subagents/{orchestrator grunt}.md`
106
119
 
107
120
  Emit: `.claude/` `.grok/` `.agents/` `.gemini/`
108
121
 
109
- - **orchestrator** (parent) — always spawn+prompt; user-facing `[orchestrator]:` (or child role tag) one-line recap; advise leftover numbered pick each on own line after that recap; one empty blank line immediately before leftover 1. (leftover last; not adjacent to recap/body) — do not cram `1. Implementer with verbal plan 2. Implementer with file plan 3. Tweak` onto the recap line. `/parent` one-turn; `/handoff` writes `.tmp/grunt/handoffs/{serial}-{slug}-{stamp}.md`; `/tmp` writes `.tmp/grunt/{serial}-{slug}-{stamp}.{ext}`; `/pickup` spawn-first pickup (inverse of `/handoff`; not a mode). `/solo` session escape; `/cascade` restores it. Small/low router. First token spawn. No parent Read/Bash/Grep. Does not implement, plan, or fetch world facts
110
- - **grunt** — tools: facts/search/exec/git/web/test/low-reason mechanical write. Isolation `verdict:`. Never feature solution. Never spawn. Snippet/cite/"what is X"/world fact: `job: web`. URL/browse/click/fill/snap/live DOM: browser rail (`node scripts/browser.mjs`; Lightpanda default); child prompt includes `.rulesync/skills/browser/SKILL.md` + `.rulesync/reference/browser.md`; never websearch that page. URL-in-a-cite ≠ browse. No `job:browse`; parent does not Skill-invoke `browser`
111
- - **implementer** — write already-defined solution on allowlisted paths. TDD when spec/plan says tests. Validate + sim after write. Fat dumps via `need:`. Never spawn. Never plan. Id is **implementer** (not Implementor)
112
- - **thinker** — think/plan/advise/recommend/how/why/explain; unsure→thinker; cheap false+; edge cases. Read-only named-file Read of prompt SSOT; trees/search/exec/web/test → `need:`. Never spawn. No bash. Never implement
122
+ - **orchestrator** — session agent. Tools on. Does the work. en-US unless asked. Concise complete sentences with natural grammar. Skip filler and fluff. Optimal solutions only; rewrite if not. Flag blockers. Do not monkey-patch. Runs `node scripts/grunt-job.mjs` for fat dumps; spawn grunt only when the dump needs judgment.
123
+ - **grunt** — isolation worker. Facts only (≤8 lines, stash receipt when needed).
113
124
 
114
- Children never spawn. Spawn only grunt|implementer|thinker. Omit model on spawn; frontmatter on agent files picks haiku/sonnet/opus vs grok-4.5 / grok-4.6 / other hosts. Voice: `.rulesync/reference/output.md`. Protocol: `.rulesync/reference/cascade.md`. `AGENTS.md`/`CLAUDE.md` spawn-first. `GEMINI.md` → `@AGENTS.md`. Goals: synced configs across grok build claude code codex gemini cli antigravity; max situational speed; superterse token savings; max/min reasoning by role.
125
+ Fat dumps rewrite to `scripts/grunt-job.mjs` (squeez + stash). Scratch goes in `.tmp/grunt/`. Voice: `.rulesync/reference/output.md`. `GEMINI.md` → `@AGENTS.md`.
115
126
 
116
127
  ## Browser
117
128
 
118
- Lightpanda-first session CLI: `node scripts/browser.mjs nav|snap|click|fill|shot|pdf|stop|doctor|ensure`. Zero MCP. Zero env knobs. Chromium only for `shot`/`pdf`/`trace`, Windows, missing Lightpanda, one probe-fail replay, or paint hosts. Session: `.tmp/grunt/browser/`. Spec: [`.rulesync/reference/browser.md`](.rulesync/reference/browser.md). Parent routes URL/browse/click/fill/snap/live DOM to grunt (paste skill+browser.md abs paths; never Skill-invoke `browser`; no `job:browse`). URL-in-a-cite ≠ browse.
129
+ Lightpanda-first session CLI: `node scripts/browser.mjs`. Zero MCP. Zero env knobs. The rail swaps to Chromium for `shot`/`pdf`/`trace`, Windows, missing Lightpanda, probe-fail, Chromium-first hosts (figma, Google docs/sheets/slides, Gmail, earth, Amazon), or a blocked/empty/client-rendered snap. Session: `.tmp/grunt/browser/`. Spec: [`.rulesync/reference/browser.md`](.rulesync/reference/browser.md). App e2e uses Playwright. URL-in-a-cite is a cite.
119
130
 
120
131
  `node scripts/browser.mjs doctor` (alias `ensure`) runs the unified doctor (`scripts/doctor.mjs`). Install engines: [Prerequisites](#prerequisites).
121
132
 
@@ -123,11 +134,15 @@ Lightpanda-first session CLI: `node scripts/browser.mjs nav|snap|click|fill|shot
123
134
 
124
135
  Present under `.claude` / `.rulesync` / `.agents` / `.grok` (`rulesync -f skills` mirrors SSOT):
125
136
 
126
- - `browser` `cascade` `commit` `commit-and-push` (1-release alias → `commit-push`) `commit-push` `commit-push-deploy` `commit-push-release` `explain` `handoff` `pickup` `parent` `solo` `tmp` `write-plan` `implement-plan`
137
+ - `ask` `auto` `browser` `clasp` `commit` `commit-and-push` (1-release alias → `commit-push`) `commit-push` `commit-push-deploy` `commit-push-release` `explain` `google-workspace` `handoff` `implement-plan` `listen` `pickup` `speak` `tmp` `write-plan`
138
+
139
+ `/auto` (default) keeps going and asks on blockers. `/ask` finishes one step, recaps, then asks. `/auto` returns the session to auto.
127
140
 
128
- Reserved names: do not reuse those stems for consumer custom skills. Same name → one SSOT under `.rulesync/skills/<name>/`; re-init force-refresh overwrites grunt-owned names; extras kept; maps `origin` badge ≠ content picker. See `.rulesync/reference/law.md` (flows into INDEX).
141
+ Reserved names: do not reuse those stems for consumer custom skills. Same name → one SSOT under `.rulesync/skills/<name>/`; re-init force-refresh overwrites grunt-owned names; extras kept; maps `origin` badge ≠ content picker. Retired 0.5 names (`parent` `solo` `cascade` plus subagents `implementer` `thinker`) are pruned on init/upgrade. See `.rulesync/reference/law.md` (flows into INDEX).
129
142
 
130
- `/write-plan` and `/implement-plan` SSOT: `.rulesync/skills/{write-plan,implement-plan}/`; format SSOT `.rulesync/reference/plan-format.md`. `/write-plan` plan-only inspect-pause → `next: /implement-plan {n}`; empty `/implement-plan` resumes unique in-progress or starts unique ready, else lists (need serial); leftover pick **2. Implementer with file plan** = persist then implement one-shot skip pause; leftover pick **1. Implementer with verbal plan** / explicit implement = verbal spec no plan file; slash `/implement-plan {n}` disk/file ≠ verbal; not `ok`/`yes`/`go`
143
+ Size first: `.rulesync/reference/scope.md`. Straight shot → do it. Else that file.
144
+
145
+ `/write-plan` and `/implement-plan` SSOT: `.rulesync/skills/{write-plan,implement-plan}/`; format SSOT `.rulesync/reference/plan-format.md`. `/write-plan` plan-only inspect-pause → `next: /implement-plan {n}`; empty `/implement-plan` resumes unique in-progress or starts unique ready, else lists (need serial); slash `/implement-plan {n}` disk/file. The session agent executes the plan. `/implement-plan` also writes `.tmp/grunt/implementations/` (format `.rulesync/reference/implementation-format.md`): Files / Log / Done for commits. Straight shots skip the journal.
131
146
 
132
147
  ## Generate
133
148
 
@@ -151,15 +166,32 @@ Emit writes other-CLI trees from `.rulesync` for the **next** process of that CL
151
166
  - `sync-globals` / `purge-mcps`: dry-run default; `--apply` writes
152
167
  - Hosts: grok claude codex gemini antigravity
153
168
 
169
+ ### Secrets (machine)
170
+
171
+ Not git. Not `sync-globals`.
172
+
173
+ First-hand: `npm exec grunt setup` (TTY). Per target: `node scripts/setup.mjs speak|listen|google-workspace|browser`. Spec: `.rulesync/reference/setup.md`.
174
+
175
+ | | where |
176
+ | --- | --- |
177
+ | Speak / listen OpenAI | `OPENAI_API_KEY` or `~/.grunt/speak.json` `openai.apiKey` |
178
+ | Speak ElevenLabs | `ELEVENLABS_API_KEY` or `~/.grunt/speak.json` `elevenlabs.apiKey` |
179
+ | Listen extras | `WHISPER_MODEL` `LISTEN_DEVICE` `LISTEN_STT` `SPEAK_PROVIDER` — optional |
180
+ | Google Workspace | OAuth under `~/.grunt/` |
181
+
182
+ `~/.grunt/speak.json` chmod 600. Env wins over that file. Copy the file or export env on another machine. Doctor reports optional google-workspace / speak / ffmpeg / whisper-cli.
183
+
184
+ `sync-globals` is host CLI globals (MCP and friends), not API keys.
185
+
154
186
  ## Architecture
155
187
 
156
- Protocol picture: one CLI host process, parent-only orchestrator in that session, three sibling spawn types (`grunt` | `implementer` | `thinker`), local workspace tools with RTK on Bash stdout only, one vendor Model API outside the host bubble. Not a product walkthrough. `@lovrozagar/grunt` = protocol SoT + CLI (init/generate/check); not a model runtime. Do not paste `.rulesync/reference/cascade.md` here — boxes and edges only.
188
+ Protocol picture: one CLI host process, session orchestrator in that session with tools on, optional grunt isolation sibling, local workspace tools with RTK on Bash stdout only, one vendor Model API outside the host bubble. Not a product walkthrough. `@lovrozagar/grunt` = protocol SoT + CLI (init/generate/check); not a model runtime. Do not paste `.rulesync/reference/cascade.md` here — boxes and edges only.
157
189
 
158
190
  ### Containment
159
191
 
160
192
  Draw **one** CLI host bubble. That bubble is **this** session’s CLI: Grok Build, Claude Code, Codex, Gemini CLI, or Antigravity. The CLI **is** the host — not a peer router beside another CLI. Parent lives **inside** that host session. Children spawn **inside the same process**. Other CLIs = emit/config on disk only; no runtime hop; no shared spawn/peek line.
161
193
 
162
- User-visible conversation attaches only to the parent. Children never talk to the user. Children never spawn.
194
+ User-visible conversation attaches only to the session agent. Children never talk to the user. Children never spawn. The session agent does the work. Spawn grunt only when isolation is cheaper.
163
195
 
164
196
  ### Host support (GAP)
165
197
 
@@ -180,30 +212,23 @@ Same topology as the session recap; every legal edge labeled.
180
212
  ```
181
213
  USER
182
214
  │
183
- │ session in/out (parent only; tagged one-line recap; advise leftover blank then 1./2./3. own lines)
215
+ │ session in/out (session agent only; tagged recap)
184
216
  │ TUI local; completion tokens from Model API (mixed)
185
217
  ▼
186
218
  ┌──────────────────────── CLI HOST (this process) ────────────────────────┐
187
219
  │ TUI · hooks · emit/generate · RTK PreToolUse · fs workspace │
188
220
  │ │
189
- │ Parent orchestrator (only spawner; spawn-first; no parent Read/Bash) │
190
- │ legal spawn types: grunt | implementer | thinker │
221
+ │ Session orchestrator (tools on; grunt-job first) │
222
+ │ legal spawn type: grunt │
191
223
  │ omit model on spawn; FM on agent files picks haiku/sonnet/opus │
192
224
  │ vs grok-4.5 / grok-4.6 / etc. │
193
225
  │ │
194
- │ spawn / peek / resume_from │
226
+ │ spawn / peek (optional) │
195
227
  │ │ │
196
228
  │ ▼ │
197
- │ siblings (never spawn; no child→child) │
198
- │ ┌───────────┬───────────────┬──────────────┐ │
199
- │ │ grunt │ implementer │ thinker │ │
200
- │ │ facts/ │ specified │ plan/deep │ │
201
- │ │ tools │ writes + TDD │ reason │ │
202
- │ │ Bash+fs │ Write/Bash/ │ Read / need: │ │
203
- │ │ web/test │ fs │ no Bash │ │
204
- │ └─────┬─────┴───────┬───────┴───────┬──────┘ │
205
- │ │ │ │ │
206
- │ └──────── tool call ──────────┘ │
229
+ │ grunt sibling (never spawn; facts ≤8 lines) │
230
+ │ │ │
231
+ │ └──────── tool call ──────────┐ │
207
232
  │ │ │
208
233
  │ ▼ │
209
234
  │ host tools (same process) │
@@ -238,48 +263,36 @@ Same topology as the session recap; every legal edge labeled.
238
263
  (no fake peeks)
239
264
  ```
240
265
 
241
- ### `need:` / `resume_from`
266
+ ### `need:` / grunt-job
242
267
 
243
- Implementer or thinker that still needs a fat dump **stops** on a `need:` JSON line. Parent fans those jobs as **parallel grunt** siblings, then one `resume_from` with **new** `verdict:` blobs only. Max **3** `resume_from` per child id. Thinker has no Bash; facts go through this loop. Grunt does not emit `need:` for its own tool use.
268
+ The session agent runs `node scripts/grunt-job.mjs --job search|exec|slice|fetch|test` for fat dumps. A grunt child that still needs a fat dump **stops** on a `need:` JSON line. SubagentStop intercepts `search|exec|slice|fetch` (cap 4) in-hook. Spawn a grunt **model** only when the dump needs judgment. Grunt does not emit `need:` for its own tool use.
244
269
 
245
270
  ```
246
- implementer | thinker
247
- │
248
- │ stop on JSON only (≤4 jobs/batch)
249
- ▼
250
- need: [{"job":"search|exec|web|test","query":"..."}]
251
- │
252
- │ parent parse-need (or SubagentStop intercept
253
- │ when every job is search|exec)
254
- ▼
255
- parallel grunt spawns (same host; not a new CLI)
271
+ session agent
256
272
  │
257
- │ isolation
273
+ │ grunt-job first
258
274
  ▼
259
- verdict: ok|fail|empty
275
+ node scripts/grunt-job.mjs --job search|exec|slice|fetch|test
260
276
  │
261
- │ one resume_from:<child id> + new verdicts only
262
- │ do not re-send original task; max 3
277
+ │ spawn grunt model only if the dump needs judgment
263
278
  ▼
264
- same child continues (transcript already on resume_from)
279
+ grunt isolation facts (≤8 lines, optional stash=)
265
280
  ```
266
281
 
267
282
  ### Node table
268
283
 
269
284
  | node | inside host? | local vs AI-server | notes |
270
285
  | --- | --- | --- | --- |
271
- | User | no | mixed | Speaks only to parent session. Never a child edge. |
272
- | Session in/out / TUI | yes | mixed | TUI is local; recap tokens come from the model. Legal `[role]:` one-line recap; advise leftover numbered pick each on own line after; one empty blank line immediately before leftover 1. (not crammed onto the recap line). |
286
+ | User | no | mixed | Speaks only to the session agent. Never a child edge. |
287
+ | Session in/out / TUI | yes | mixed | TUI is local; recap tokens come from the model. Legal `[role]:` tagged recap. |
273
288
  | CLI host process | yes (is the box) | local process | Grok Build / Claude Code / Codex / Gemini / Antigravity. Not a peer of another CLI. |
274
- | Parent orchestrator | yes | AI-server **turn** | Spawn-first low router. No parent Read/Bash/Grep. Lives in this session, not a sidecar. |
275
- | `grunt` sibling | yes | AI-server **turn** | Facts/tools/mechanical write. Never spawn. Never feature solution. |
276
- | `implementer` sibling | yes | AI-server **turn** | Specified writes + TDD when tests are in the spec. Agent id is **implementer**. Never spawn. |
277
- | `thinker` sibling | yes | AI-server **turn** | Plan/deep reason. Read-only. Named-file Read of prompt SSOT; investigate → `need:`. No Bash. Never spawn. |
278
- | Host spawn / peek / `resume_from` | yes | **local** | `spawn_subagent` / `Agent` and host peek tools. Gemini spawn/peek = GAP; block on return, classify `done`; no fake peeks. |
279
- | Workspace fs tools | yes | **local** | Read/Grep/Glob/Write (and host aliases). Parent is denied these except documented persist paths / `/solo`. |
280
- | Bash / `run_terminal_command` | yes | **local** | RTK wraps stdout on PreToolUse. Thinker has no Bash. |
289
+ | Session orchestrator | yes | AI-server **turn** | Does the work. Tools on. grunt-job first. Lives in this session, not a sidecar. |
290
+ | `grunt` sibling | yes | AI-server **turn** | Facts/tools/mechanical write. Never spawn. Never feature solution. Optional. |
291
+ | Host spawn / peek | yes | **local** | `spawn_subagent` / `Agent` and host peek tools. Gemini spawn/peek = GAP; block on return, classify `done`; no fake peeks. Other hosts: spawn is an optimization; grunt-job still runs in-process. |
292
+ | Workspace fs tools | yes | **local** | Read/Grep/Glob/Write (and host aliases). Fat dumps still gate. |
293
+ | Bash / `run_terminal_command` | yes | **local** | RTK wraps stdout on PreToolUse. |
281
294
  | RTK | yes | **local** | Bash/shell stdout compression only. Not Read/Grep/Glob/prompts/images. |
282
- | `scrub-spawn-prompt` / `parse-need` / `grunt-job` | yes | **local** | Hooks and scripts. Isolation `verdict:` is grunt output, not a model hop. |
295
+ | `scrub-spawn-prompt` / `parse-need` / `grunt-job` | yes | **local** | Hooks and scripts. Isolation facts are grunt-job output, not a model hop. |
283
296
  | emit / generate / init | yes (this repo / install) | **local** | Writes other-CLI configs. Not a runtime line to those CLIs. |
284
297
  | WebSearch / web_fetch | tool from host | **remote-not-LLM** | Network search/fetch. Not a second Model API box. Snippet/cite/"what is X"/world fact: grunt `job: web`. Live URL/DOM: grunt browser rail, not this box. |
285
298
  | MCP | policy deny | n/a | Denied by policy in-tree. Do not draw as a main path. |
@@ -293,12 +306,11 @@ Use these labels. Do not revive “prompt input” or “agent to use”.
293
306
 
294
307
  | edge | meaning |
295
308
  | --- | --- |
296
- | **session in/out** | User ↔ parent only. Children have no user edge. |
297
- | **spawn** | Parent → `grunt` \| `implementer` \| `thinker`. First sentence: `You are {agent} subagent.` Omit model. |
298
- | **peek** | Parent reads host status on the child id. Real host fields. `timeout_ms=60000`. GAP hosts: no fake peeks; block on spawn return = `done`. |
299
- | **resume_from** | Parent continues the **same** child id with new `verdict:` blobs only. Max 3. Not a fresh spawn. |
300
- | **need:** | Child stop line: fat dump jobs for parent to fan as grunt. Cap 4 jobs per batch. |
301
- | **verdict:** | Grunt isolation result back onto `resume_from`. `ok\|fail\|empty`. Short facts, not dumps. |
309
+ | **session in/out** | User ↔ session agent only. Children have no user edge. |
310
+ | **spawn** | Session agent → `grunt`. First sentence: `You are grunt subagent.` Omit model. Optional. |
311
+ | **peek** | Session agent reads host status on the child id. Real host fields. `timeout_ms=60000`. GAP hosts: no fake peeks; block on spawn return = `done`. |
312
+ | **need:** | Child stop line: fat dump jobs interceptable as grunt-job. Cap 4 jobs per batch. |
313
+ | **facts** | Grunt isolation result back onto `resume_from`. Sentence plus dash facts, not dumps. |
302
314
  | **tool call** | Child (or, illegally if parent, denied) → host tools. |
303
315
  | **RTK** | PreToolUse Bash/shell → compressed stdout. No other tools. |
304
316
  | **completion** | Parent and children → **one** Model API (this host’s SDK). |
@@ -307,13 +319,12 @@ Use these labels. Do not revive “prompt input” or “agent to use”.
307
319
  ### What not to draw
308
320
 
309
321
  - A **CLI Provider** box as a peer of the host, or a router that hops between CLIs at runtime
310
- - **Thinker ↔ CLI** as a special extra channel — thinker is a sibling; tools/`need:` like the protocol
311
- - The spelling **Implementor** — id is **implementer**
322
+ - **implementer** or **thinker** siblings — gone in 0.6
312
323
  - **Per-agent Model API** boxes — one Model API outside the host, this SDK only
313
- - **Child → child spawn** — isolation is parent fan-out of grunt siblings (or in-hook grunt-job for interceptable `search|exec` batches)
324
+ - **Child → child spawn** — isolation is grunt-job or a grunt sibling
314
325
  - **MCP** as a happy-path tool rail
315
326
  - **RTK** on Read/Grep/Glob/prompts/images
316
- - **User** arrows into grunt/implementer/thinker
327
+ - **User** arrows into grunt
317
328
  - Gemini **fake peek** loops. GAP: no invented status API; block on return
318
329
 
319
330
  ### Emit footnote and Gemini GAP
@@ -328,17 +339,15 @@ Repo-relative (repository root):
328
339
 
329
340
  - `.rulesync/reference/INDEX.md` — aggregate catalog composed from slices (law.md, skills-map.md, refs-map.md). Maps/law = slices for deep dive. Always. Not if-maps-else
330
341
  - `.rulesync/reference/law.md` — domain law stub (protocol stays cascade/overview)
331
- - `.rulesync/reference/cascade.md` — parent-only spawn, peek/kill table, `need:` / `resume_from`, isolation `verdict:`
342
+ - `.rulesync/reference/cascade.md` — parent-only spawn, peek/kill table, `need:` / `resume_from`, isolation facts
332
343
  - `.rulesync/reference/rtk.md` — Bash/shell stdout compression
333
344
  - `.rulesync/reference/map.md` — cheap outline of protocol, scripts, generated trees
334
345
  - `.rulesync/subagents/orchestrator.md`
335
346
  - `.rulesync/subagents/grunt.md`
336
- - `.rulesync/subagents/implementer.md`
337
- - `.rulesync/subagents/thinker.md`
338
347
 
339
348
  ## Layout
340
349
 
341
- Published (`package.json` `files`): `bin/grunt.js` `cli` `scripts/check-globals.mjs` `scripts/emit-agent-shell-tools.mjs` `scripts/emit-gemini.mjs` `scripts/guarded-roots.mjs` `scripts/emit-mcp-policy.mjs` `scripts/gate-fat-tools.mjs` `scripts/hooks-union.mjs` `scripts/pipeline.mjs` `scripts/grunt-job.mjs` `scripts/parse-need.mjs` `scripts/persist-handoff.mjs` `scripts/persist-tmp.mjs` `scripts/persist-plan.mjs` `scripts/purge-global-mcps.mjs` `scripts/scrub-spawn-prompt.mjs` `scripts/scrub-text-lib.mjs` `scripts/sync-global-settings.mjs` `scripts/browser.mjs` `scripts/doctor.mjs` `scripts/scrub-text` `.rulesync` `.grok` `.codex` `.claude` `.agents` `AGENTS.md` `CLAUDE.md` `.mcp.json` `README.md` `LICENSE` `CHANGELOG.md`
350
+ Published (`package.json` `files`): `bin/grunt.js` `cli` `scripts/check-globals.mjs` `scripts/emit-agent-shell-tools.mjs` `scripts/emit-gemini.mjs` `scripts/emit-maps.mjs` `scripts/guarded-md.mjs` `scripts/guarded-roots.mjs` `scripts/emit-mcp-policy.mjs` `scripts/gate-fat-tools.mjs` `scripts/hooks-union.mjs` `scripts/pipeline.mjs` `scripts/grunt-job.mjs` `scripts/parse-need.mjs` `scripts/persist-handoff.mjs` `scripts/persist-implementation.mjs` `scripts/persist-tmp.mjs` `scripts/persist-plan.mjs` `scripts/purge-global-mcps.mjs` `scripts/scrub-spawn-prompt.mjs` `scripts/scrub-text-lib.mjs` `scripts/sync-global-settings.mjs` `scripts/browser.mjs` `scripts/speak.mjs` `scripts/listen.mjs` `scripts/google-workspace.mjs` `scripts/interactive.mjs` `scripts/prompt.mjs` `scripts/setup.mjs` `scripts/doctor.mjs` `scripts/skill-conflicts.mjs` `scripts/scrub-text` `.rulesync` `.grok` `.codex` `.claude` `.agents` `AGENTS.md` `CLAUDE.md` `.mcp.json` `README.md` `LICENSE` `CHANGELOG.md`
342
351
 
343
352
  No `scripts/*.test.ts` `scripts/fixtures/` `docs/` `coverage/` `vitest.config.ts` in `files`. `cli` dir ships whole (includes `cli/*.test.ts`).
344
353
 
@@ -371,10 +380,10 @@ npm i && npm test
371
380
 
372
381
  Keep these four flows only:
373
382
 
374
- 1. “Create me a react weather app” → thinker plan/recap-stop + pick 1.Implementer with verbal plan 2.Implementer with file plan 3.Tweak → user picks 1 (verbal spec) or 2 (file plan one-shot) or `/implement-plan {n}` → implementer write → recap
375
- 2. “What is 2+2” → grunt → `[grunt]:` echo
376
- 3. Marvel theatrical next → grunt `job:web` → recap
377
- 4. `.logs` 3/6/2021 tag `framework bug` → grunt local search → recap
383
+ 1. “Create me a react weather app” → session agent writes (or `/write-plan` then `/implement-plan {n}`) → recap
384
+ 2. “What is 2+2” → session agent → recap
385
+ 3. Marvel theatrical next → session agent web search → recap
386
+ 4. `.logs` 3/6/2021 tag `framework bug` → `grunt-job --job search` → recap
378
387
 
379
388
  ## License
380
389
 
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, shouldAutoSkipGlobals, toGruntScriptName } from "./init.mjs"
5
+ import { destAlreadyInited, init, RESERVED_SKILLS, shouldAutoSkipGlobals, 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)), "..")
@@ -18,6 +18,8 @@ Commands:
18
18
  sync-globals npm run grunt:sync:globals (dry-run; --apply to write)
19
19
  purge-mcps npm run grunt:purge:global-mcps (dry-run; --apply to write)
20
20
  doctor npm run grunt:doctor
21
+ setup npm run grunt:setup — handheld keys/OAuth (speak, listen, google-workspace, browser)
22
+ upgrade Re-init: copy owned files, prune retired grunt-owned names, print reserved skills
21
23
  help Show this help
22
24
  version Print package version
23
25
 
@@ -36,6 +38,8 @@ const MENU_OPTIONS = [
36
38
  { value: "sync-globals", label: "sync-globals" },
37
39
  { value: "purge-mcps", label: "purge-mcps" },
38
40
  { value: "doctor", label: "doctor" },
41
+ { value: "setup", label: "setup" },
42
+ { value: "upgrade", label: "upgrade" },
39
43
  { value: "help", label: "help" },
40
44
  { value: "quit", label: "quit" },
41
45
  ]
@@ -90,7 +94,7 @@ export function parseArgv(argv) {
90
94
  }
91
95
  positionals.push(a)
92
96
  }
93
- return { cmd: positionals[0], skipGlobals, apply, host, hostError }
97
+ return { cmd: positionals[0], args: positionals.slice(1), skipGlobals, apply, host, hostError }
94
98
  }
95
99
 
96
100
  function hostExtra(host) {
@@ -105,6 +109,8 @@ function bindSpinner() {
105
109
  }
106
110
  }
107
111
 
112
+ export const APPLY_GLOBALS_CONFIRM = "Apply global prompt optimizations? (recommended)"
113
+
108
114
  async function runInit(cwd, { skipGlobals, interactive }) {
109
115
  if (!interactive) {
110
116
  init(cwd, { skipGlobals })
@@ -119,7 +125,7 @@ async function runInit(cwd, { skipGlobals, interactive }) {
119
125
  }
120
126
  const autoSkip = skipGlobals || shouldAutoSkipGlobals(cwd)
121
127
  const applyGlobals = await confirm({
122
- message: "Apply globals?",
128
+ message: APPLY_GLOBALS_CONFIRM,
123
129
  initialValue: !autoSkip,
124
130
  })
125
131
  init(cwd, {
@@ -166,6 +172,18 @@ async function dispatch(cmd, flags, interactive) {
166
172
  npmRun(toGruntScriptName("doctor"))
167
173
  return
168
174
  }
175
+ if (cmd === "setup") {
176
+ npmRun(toGruntScriptName("setup"), flags.args)
177
+ return
178
+ }
179
+ if (cmd === "upgrade") {
180
+ await runInit(process.cwd(), {
181
+ skipGlobals: flags.skipGlobals,
182
+ interactive: false,
183
+ })
184
+ process.stdout.write(`reserved: ${RESERVED_SKILLS.join(" ")}\n`)
185
+ return
186
+ }
169
187
  process.stdout.write(USAGE)
170
188
  process.exitCode = 1
171
189
  }
package/cli/grunt.test.ts CHANGED
@@ -23,6 +23,7 @@ vi.mock("./init.mjs", () => ({
23
23
  destAlreadyInited,
24
24
  shouldAutoSkipGlobals,
25
25
  toGruntScriptName,
26
+ RESERVED_SKILLS: ["browser", "tmp", "write-plan"],
26
27
  GRUNT_NPM_PREFIX: "grunt:",
27
28
  }));
28
29
  vi.mock("node:child_process", () => ({ execFileSync }));
@@ -34,7 +35,7 @@ vi.mock("./prompt.mjs", () => ({
34
35
  bailIfCancel,
35
36
  }));
36
37
 
37
- import { parseArgv, start } from "./grunt.mjs";
38
+ import { APPLY_GLOBALS_CONFIRM, parseArgv, start } from "./grunt.mjs";
38
39
 
39
40
  const USAGE = `Usage: grunt [command]
40
41
 
@@ -47,6 +48,8 @@ Commands:
47
48
  sync-globals npm run grunt:sync:globals (dry-run; --apply to write)
48
49
  purge-mcps npm run grunt:purge:global-mcps (dry-run; --apply to write)
49
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
50
53
  help Show this help
51
54
  version Print package version
52
55
 
@@ -254,6 +257,34 @@ describe("start", () => {
254
257
  });
255
258
  });
256
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
+
257
288
  it("unknown writes usage and exitCode 1", async () => {
258
289
  process.argv = ["node", "grunt", "nope"];
259
290
  await start();
@@ -282,11 +313,17 @@ describe("start", () => {
282
313
  "sync-globals",
283
314
  "purge-mcps",
284
315
  "doctor",
316
+ "setup",
317
+ "upgrade",
285
318
  "help",
286
319
  "quit",
287
320
  ]);
288
321
  expect(init).toHaveBeenCalled();
289
322
  expect(isInteractive).toHaveBeenCalledOnce();
323
+ expect(confirm).toHaveBeenCalledWith({
324
+ message: APPLY_GLOBALS_CONFIRM,
325
+ initialValue: true,
326
+ });
290
327
  });
291
328
 
292
329
  it("TTY menu generate", async () => {
@@ -360,9 +397,12 @@ describe("start", () => {
360
397
  await start();
361
398
  expect(confirm).toHaveBeenCalled();
362
399
  const globalsConfirm = confirm.mock.calls.find(
363
- (c) => String((c[0] as { message?: string }).message).includes("globals"),
400
+ (c) => (c[0] as { message?: string }).message === APPLY_GLOBALS_CONFIRM,
364
401
  );
365
- expect(globalsConfirm?.[0]).toMatchObject({ initialValue: false });
402
+ expect(globalsConfirm?.[0]).toMatchObject({
403
+ message: APPLY_GLOBALS_CONFIRM,
404
+ initialValue: false,
405
+ });
366
406
  expect(init).toHaveBeenCalledWith(
367
407
  process.cwd(),
368
408
  expect.objectContaining({
@@ -382,6 +422,10 @@ describe("start", () => {
382
422
  confirm.mockResolvedValue(false);
383
423
  process.argv = ["node", "grunt", "init", "--skip-globals"];
384
424
  await start();
425
+ expect(confirm).toHaveBeenCalledWith({
426
+ message: APPLY_GLOBALS_CONFIRM,
427
+ initialValue: false,
428
+ });
385
429
  expect(init).toHaveBeenCalledWith(
386
430
  process.cwd(),
387
431
  expect.objectContaining({