@lovrozagar/grunt 0.2.0 → 0.3.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 (55) hide show
  1. package/.agents/agents/grunt.md +4 -2
  2. package/.agents/agents/orchestrator.md +2 -2
  3. package/.agents/hooks.json +12 -1
  4. package/.agents/skills/commit/SKILL.md +21 -0
  5. package/.agents/skills/commit-and-push/SKILL.md +15 -0
  6. package/.agents/skills/explain/SKILL.md +14 -0
  7. package/.agents/skills/handoff/SKILL.md +84 -0
  8. package/.agents/skills/parent/SKILL.md +11 -0
  9. package/.agents/skills/solo/SKILL.md +21 -0
  10. package/.claude/agents/grunt.md +2 -0
  11. package/.claude/agents/orchestrator.md +3 -3
  12. package/.claude/settings.json +34 -1
  13. package/.claude/skills/commit/SKILL.md +21 -0
  14. package/.claude/skills/commit-and-push/SKILL.md +15 -0
  15. package/.claude/skills/explain/SKILL.md +14 -0
  16. package/.claude/skills/handoff/SKILL.md +84 -0
  17. package/.claude/skills/parent/SKILL.md +11 -0
  18. package/.claude/skills/solo/SKILL.md +21 -0
  19. package/.codex/agents/grunt.toml +4 -2
  20. package/.codex/agents/orchestrator.toml +2 -2
  21. package/.codex/hooks.json +34 -1
  22. package/.grok/agents/grunt.md +4 -2
  23. package/.grok/agents/orchestrator.md +2 -2
  24. package/.grok/hooks/orchestrate-parent.js +263 -29
  25. package/.grok/skills/commit/SKILL.md +21 -0
  26. package/.grok/skills/commit-and-push/SKILL.md +15 -0
  27. package/.grok/skills/handoff/SKILL.md +84 -0
  28. package/.grok/skills/solo/SKILL.md +21 -0
  29. package/.rulesync/hooks.jsonc +11 -3
  30. package/.rulesync/reference/cascade.md +4 -3
  31. package/.rulesync/reference/hooks.md +5 -1
  32. package/.rulesync/reference/map.md +18 -3
  33. package/.rulesync/rules/CLAUDE.md +2 -1
  34. package/.rulesync/rules/overview.md +2 -1
  35. package/.rulesync/skills/commit/SKILL.md +22 -0
  36. package/.rulesync/skills/commit-and-push/SKILL.md +16 -0
  37. package/.rulesync/skills/handoff/SKILL.md +84 -0
  38. package/.rulesync/skills/solo/SKILL.md +21 -0
  39. package/.rulesync/subagents/grunt.md +6 -2
  40. package/.rulesync/subagents/implementer.md +2 -0
  41. package/.rulesync/subagents/orchestrator.md +4 -2
  42. package/.rulesync/subagents/thinker.md +2 -0
  43. package/AGENTS.md +2 -1
  44. package/CLAUDE.md +2 -1
  45. package/README.md +2 -2
  46. package/cli/init.mjs +105 -6
  47. package/cli/init.test.ts +303 -6
  48. package/package.json +12 -6
  49. package/scripts/emit-agent-shell-tools.mjs +118 -0
  50. package/scripts/emit-gemini.mjs +219 -0
  51. package/scripts/emit-mcp-policy.mjs +23 -0
  52. package/scripts/gate-fat-tools.mjs +64 -0
  53. package/scripts/hooks-union.mjs +391 -0
  54. package/scripts/persist-handoff.mjs +231 -0
  55. package/scripts/scrub-spawn-prompt.mjs +37 -17
@@ -22,13 +22,14 @@ Cheap outline. Not a file dump.
22
22
  ## Grok hand files
23
23
  - `.grok/hooks/orchestrate-parent.js` + `orchestrate-parent.json` — parent spawn/fat/stop/plan-write / SubagentStop intercept
24
24
  - `.grok/hooks/rtk.json` — RTK
25
- - `.grok/skills/{write-plan,implement-plan,shared,parent}/` — `/parent` one-turn; point to cascade, do not paste
25
+ - `.grok/skills/{write-plan,implement-plan,shared}/` — hand-only (no rulesync SSOT); point to cascade, do not paste
26
+ - `.grok/skills/{parent,explain,handoff,solo,commit,commit-and-push}/` — generated from `.rulesync/skills/`; do not hand-edit
26
27
  - `.grok/parent.md` — 1-line pointer to orchestrator agent; not SessionStart
27
28
  - `.grok/roles/*.toml`
28
29
  - `.grok/global-settings.toml` — merged into `~/.grok/config.toml` by `scripts/sync-global-settings.mjs` (not auto-loaded; project config cannot set `[features]`)
29
30
  - `.rulesync/global-settings/` — host manifest and reserved noop payloads
30
31
  - `.rulesync/mcp-policy.jsonc` — MCP deny-default SSOT (`default: deny`, `allow: []`)
31
- - `.rulesync/skills/parent/` — `/parent` skill SSOT; copy lives in `.grok/skills/parent/`
32
+ - `.rulesync/skills/{parent,explain,handoff,solo,commit,commit-and-push}/` — skill SSOT; `rulesync -f skills` emits `.grok/skills/`, `.claude/skills/`, `.agents/skills/` byte-equal. `/cascade` has no skill (hook-only: a `grunt`-adjacent listing entry risks mis-routing `Skill` vs `Agent`)
32
33
 
33
34
  ## Scripts
34
35
  - `scripts/check-globals.mjs` — `$HOME/.grok/config.toml` `[agent].name==orchestrator` + `[features].two_pass_compaction==true`; project `.grok/config.toml` must not have `[features]`/`[agent]`; wired as `sync:globals:check` (also after emit-mcp in `rulesync:check`)
@@ -36,17 +37,28 @@ Cheap outline. Not a file dump.
36
37
  - `scripts/scrub-spawn-prompt.mjs`
37
38
  - `scripts/grunt-job.mjs` — flags `--job` `--query` `--path` `--glob` (repeatable) `--cwd`; regex in `--query` OK; never `cd &&`; unknown flags / exec shell-meta → FALLBACK
38
39
  - `scripts/parse-need.mjs`
40
+ - `scripts/persist-handoff.mjs` — `.tmp/grunt/handoffs/{serial}-{slug}-{stamp}.md`; `HANDOFF_NAME:` → slug; H2 `Goal|State|Context|Next|Watch-outs`; `status` `open|resumed|done`
39
41
  - `scripts/persist-plan.mjs`
40
42
  - `scripts/sync-global-settings.mjs` — dry-run-by-default merge of `.grok/global-settings.toml` into `$HOME/.grok/config.toml`; manifest in `.rulesync/global-settings/`; `skipKeyPattern` skips MCP; never copies project MCP into `$HOME/.grok`
41
43
  - `scripts/purge-global-mcps.mjs` — dry-run-by-default purge of stubborn global MCP sources (`$HOME/.grok/config.toml` plugins + `disabled_mcp_servers`, `$HOME/.cursor/mcp.json` `MCP_DOCKER`); `--apply` writes; never project MCP
42
- - `scripts/emit-mcp-policy.mjs` — emit project MCP deny-default from `.rulesync/mcp-policy.jsonc` (not `rulesync -f mcp`)
44
+ - `scripts/emit-mcp-policy.mjs` — emit project MCP deny-default from `.rulesync/mcp-policy.jsonc` (not `rulesync -f mcp`); owns `.gemini/settings.json` `mcpServers` (merge extra keys)
45
+ - `scripts/emit-gemini.mjs` — `GEMINI.md` (`@AGENTS.md`) + `.gemini/agents/{id}/agent.md` from `.rulesync/subagents` (not `rulesync -t geminicli`); no prune; does not touch settings
46
+ - `scripts/emit-agent-shell-tools.mjs` — after rulesync subagents: rewrite `.claude/agents/grunt.md` body `Bash`; grok/codex/gemini/generic keep `run_terminal_command`
43
47
  - `scripts/telemetry.mjs` — append-only `.tmp/orchestrator-logs/telemetry.ndjson` from hooks / grunt-job CLI; fail-open; never stdout
44
48
 
49
+ ## Tmp
50
+ - `.tmp/plans/` — persist-plan / parent / implementer
51
+ - `.tmp/orchestrator-logs/` — hooks + grunt-job telemetry (shared; not grunt scratch)
52
+ - `.tmp/grunt/` — grunt scratch/tmp only
53
+ - `.tmp/grunt/handoffs/` — persist-handoff / `/handoff`; own serial counter (not plan serials)
54
+
45
55
  ## Stamps (`.tmp/orchestrator-logs/`)
46
56
  - `tools-used` / `stop-block` / `need-intercept` / `parent-escape` — Stop does not waive impl finals for `tools-used`; `/parent` writes `parent-escape-{sid}` once
57
+ - `grunt-off-{sid}` — `/solo` session mode. Sticky: only `/solo` / `/cascade` move it. Requires a real sid (never the `default` fallback). Read fail-closed — an unreadable stamp keeps grunt on
47
58
 
48
59
  ## Generated (do not hand-edit; committed, not gitignored)
49
60
  - `AGENTS.md`, `CLAUDE.md`
61
+ - `.claude/skills/*`, `.agents/skills/*`, `.grok/skills/{parent,explain,handoff,solo,commit,commit-and-push}/` (from `.rulesync/skills/`)
50
62
  - `.grok/agents/*`
51
63
  - `.claude/agents/*`
52
64
  - `.codex/agents/*`
@@ -56,3 +68,6 @@ Cheap outline. Not a file dump.
56
68
  - `.claude/settings.json` MCP keys (`enableAllProjectMcpServers`, `enabledMcpjsonServers`, `permissions.deny` `mcp__*`); Agent denies/hooks stay
57
69
  - `.codex/config.toml` `mcp_servers` (absent when allow empty); keep `[agents] max_depth`
58
70
  - `.agents/mcp_config.json` — `mcpServers` from policy allow only
71
+ - `GEMINI.md` — `@AGENTS.md` pointer (`emit-gemini.mjs`)
72
+ - `.gemini/agents/{orchestrator,implementer,thinker,grunt}/agent.md` — `name` `description` `model` only (`emit-gemini.mjs`)
73
+ - `.gemini/settings.json` — `mcpServers` from policy allow only (`emit-mcp-policy.mjs`; extra keys preserved)
@@ -4,10 +4,11 @@ targets:
4
4
  - claudecode
5
5
  ---
6
6
  Voice: `.rulesync/reference/output.md`
7
+ All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
7
8
  Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
8
9
  Parent = this file not `.claude/agents/orchestrator.md`. You do not talk. Spawn wait echo.
9
10
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
10
- First token = spawn. No try-then-spawn. No parent probe. After child: `[agent]:` echo only. No skip-spawn. /parent = one-turn.
11
+ First token = spawn. No try-then-spawn. No parent probe. After child: `[orchestrator]:` (or child role tag) echo only. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`, recap `[handoff]:`.
11
12
  grunt ← tools (facts/search/exec/git/web/test/low-reason mechanical write; world fact: `job: web` never memory). implementer ← write defined solution. thinker ← plan/deep reason.
12
13
  Child prompt = `You are {agent} subagent.` + task + abs paths + verdicts only.
13
14
  Peek every 60s/child; quote real host fields; `done|alive|stuck`; no invent; no auto-kill. `timeout_ms=60000` every peek. GAP: block on spawn return = `done`. Stuck/blocked: quote host fields only.
@@ -9,10 +9,11 @@ globs:
9
9
  - "**/*"
10
10
  ---
11
11
  Voice: `.rulesync/reference/output.md` once.
12
+ All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
12
13
  Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
13
14
  Parent = orchestrator. You do not talk. Spawn wait echo.
14
15
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
15
- First token = spawn. No try-then-spawn. No parent probe. After child: `[agent]:` echo only. No skip-spawn. /parent = one-turn.
16
+ First token = spawn. No try-then-spawn. No parent probe. After child: `[orchestrator]:` (or child role tag) echo only. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`, recap `[handoff]:`.
16
17
  grunt ← tools (facts/search/exec/git/web/test/low-reason mechanical write; world fact: `job: web` never memory). implementer ← write defined solution. thinker ← plan/deep reason.
17
18
  Child prompt = `You are {agent} subagent.` + task + abs paths + verdicts only.
18
19
  Peek every 60s/child; quote real host fields; `done|alive|stuck`; no invent; no auto-kill. `timeout_ms=60000` every peek. GAP: block on spawn return = `done`. Stuck/blocked: quote host fields only.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: commit
3
+ description: >
4
+ Inspect the diff and commit with a Conventional Commits subject line. Use for
5
+ /commit, "commit this", "commit these changes". Never pushes.
6
+ ---
7
+
8
+ Inspect `git status` + `git diff` (staged and unstaged) before the message. Never push.
9
+
10
+ Stage only user intent. Never blind `git add -A` / `git add .`. Split unrelated concerns into multiple commits.
11
+
12
+ Message = Conventional Commits **subject only**: `type(scope): subject` (scope optional).
13
+
14
+ Types: feat fix docs style refactor perf test build ci chore revert.
15
+
16
+ Subject: imperative lowercase no trailing period.
17
+
18
+ NO body. NO footer. NO `Co-Authored-By`. NO `Generated with`. NO 🤖. NO trailers. No AI attribution. Work belongs to the human.
19
+
20
+ Never `git push`.
21
+
22
+ Protocol: `.rulesync/reference/cascade.md`. Do not paste cascade.
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: commit-and-push
3
+ description: >
4
+ Commit like /commit, then push to the current branch's upstream. Use for
5
+ /commit-and-push, "commit and push", "commit this and push".
6
+ ---
7
+
8
+ Run commit skill rules first (`.rulesync/skills/commit/SKILL.md`). Then push.
9
+
10
+ If on default branch, create/switch to a feature branch first — never push default directly.
11
+
12
+ Push upstream; if no upstream `git push -u origin <current-branch>`.
13
+
14
+ Never force-push.
15
+
16
+ Protocol: `.rulesync/reference/cascade.md`. Do not paste cascade.
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: handoff
3
+ description: >
4
+ Parent writes a session handoff under .tmp/grunt/handoffs/ and tells the user
5
+ to continue in a fresh session. Use for /handoff, "context is getting long",
6
+ "hand this off", "start a new session". Not a plan, not a design doc.
7
+ ---
8
+ # handoff
9
+
10
+ One turn, in-parent. Parent authors from its own transcript — no child sees this session, so **do not spawn**. Write once, report path, stop.
11
+
12
+ ## Invocation
13
+
14
+ `/handoff [focus]`
15
+
16
+ Focus narrows what the handoff carries. Empty → whole session.
17
+
18
+ ## When
19
+
20
+ Context large + work unfinished → write handoff, then advise a new session. Also on host/tool churn, or before a long unrelated detour.
21
+
22
+ Nothing done yet (no edits, no findings) → `nothing to hand off` and stop. Do not write an empty file.
23
+
24
+ ## Path
25
+
26
+ `.tmp/grunt/handoffs/{serial}-{slug}-{YYYYMMDDTHHMMSSZ}.md`
27
+
28
+ - serial: unpadded int ≥ 1, next after existing handoffs (own counter; not plan serials)
29
+ - slug: lower, non `[a-z0-9]` → `-`, collapse, trim 50
30
+ - stamp: `created` with `-` and `:` dropped (`20260827T143000Z`)
31
+
32
+ ## Format
33
+
34
+ ```yaml
35
+ ---
36
+ serial: 1
37
+ name: <slug>
38
+ status: open # open | resumed | done
39
+ created: 2026-08-27T14:30:00Z # UTC, seconds, no ms
40
+ source: "<≤120 chars of the session task>"
41
+ ---
42
+ ```
43
+
44
+ Headings, this order only: `# {slug}`, `## Goal`, `## State`, `## Context`, `## Next`, `## Watch-outs`.
45
+
46
+ | Section | Amount |
47
+ |---|---|
48
+ | Goal | 1–3 sentences. What the session is trying to land. |
49
+ | State | bullets. Done / changed / green-red, with absolute paths. |
50
+ | Context | bullets. Abs paths, decisions, verdict facts the next session needs. |
51
+ | Next | checkbox steps, `N [ ]` phase + `N.M [ ]` leaf, all `[ ]` fresh |
52
+ | Watch-outs | bullets. Footguns. `(none)` allowed. |
53
+
54
+ Boxes: `^{id} [ ] {text}$`, id `N` / `N.M` / `N.M.K`. Not `- [ ]`, not `1. [ ]`. Every phase has ≥1 leaf. Fresh handoff has zero `[x]`.
55
+
56
+ Facts only. Absolute paths. No transcript paste, no dump, no secrets/tokens.
57
+
58
+ ## Write
59
+
60
+ Line 1 of the body: `HANDOFF_NAME: <3-6 word name>`, then the body only.
61
+
62
+ Parent `write`s under `.tmp/grunt/handoffs/` (any filename in that dir). Grok `orchestrate-parent.js` runs `scripts/persist-handoff.mjs`: serial/slug from `HANDOFF_NAME:` / heading, injects frontmatter, rewrites path + content. Invalid handoff or a write outside that dir is denied — fix the body, do not retry elsewhere.
63
+
64
+ Host without that hook: name the file per **Path** yourself, or pipe the body to `node scripts/persist-handoff.mjs --workspace {repo}` and use its `path`.
65
+
66
+ ## Report
67
+
68
+ ```
69
+ [handoff]: serial={int} path=.tmp/grunt/handoffs/{serial}-{slug}-{stamp}.md
70
+ next: start a new session; first action = read that path
71
+ ```
72
+
73
+ Do not dump the handoff body.
74
+
75
+ ## Picking one up
76
+
77
+ New session, `/handoff` not needed: read the newest `.tmp/grunt/handoffs/*.md` (or the given serial), set `status: resumed`, work `Next` leaves in order, flip only the box. All `[x]` → `status: done`. Do not re-plan; a stale leaf → `/write-plan` a follow-up.
78
+
79
+ ## Rules
80
+
81
+ - No spawn. One write. `/handoff` is not a mode.
82
+ - Handoff ≠ plan: no Steps/Verify phases, no `.tmp/plans/` write.
83
+ - Never rewrite or renumber an existing handoff; new session = new serial.
84
+ - Protocol: `.rulesync/reference/cascade.md`. Do not paste it here.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: solo
3
+ description: >
4
+ Session mode. /solo disables the grunt cascade for this session — one normal
5
+ agent, no spawn, no orchestrator. /cascade restores it. Unlike /parent and
6
+ /explain this IS a mode: it persists until /cascade or session end.
7
+ ---
8
+ Session mode, not one-turn. Persists until `/cascade` or session end.
9
+
10
+ This session: cascade suspended. You are one normal agent.
11
+
12
+ - Do not spawn. No grunt|implementer|thinker.
13
+ - No recap tags. Do not recap `[orchestrator]:`. Answer the user directly.
14
+ - No spawn-first rule. No `need:`/`verdict:` grammar.
15
+ - Voice unchanged: `.rulesync/reference/output.md`. `/explain` is still the voice escape.
16
+
17
+ Fat-tool caps stay on — token guards, not cascade.
18
+
19
+ Claude/Codex/Grok: `/solo` via submit hook writes `.tmp/orchestrator-logs/grunt-off-{sid}`; `/cascade` unlinks it and the next turn is orchestrated again. Agents/Antigravity: instruction-only (this skill body); cannot create stamp via `/solo`; Stop still honors a pre-existing stamp if present. Session-scoped, never global.
20
+
21
+ For advisory/design turns, debugging the cascade itself, or back-and-forth where spawning costs more than it saves.
@@ -27,18 +27,22 @@ antigravity-cli:
27
27
  inheritMcp: false
28
28
  commandExecutionPolicy: eager
29
29
  tools: [view_file, grep_search, run_command, replace_file_content]
30
+ geminicli:
31
+ model: gemini-2.5-flash
30
32
  ---
31
33
  Voice: `.rulesync/reference/output.md` — must follow.
32
34
 
33
- Tools (shell/host; and more): Read, Grep, Glob, Bash, Write, Edit, Web. Git. Test runners (vitest, playwright, and similar). Other system tools. Commands of project dependencies. Installing and managing dependencies. Running dev, build, preview, deploys and similar.
35
+ Tools (shell/host; and more): read_file, grep, list_dir, run_terminal_command, write, search_replace, web_search, web_fetch. Git. Test runners (vitest, playwright, and similar). Other system tools. Commands of project dependencies. Installing and managing dependencies. Running dev, build, preview, deploys and similar.
36
+ You CAN run npm/git/bash via `run_terminal_command`. Do NOT wait on MCP. Do NOT request MCP for shell. `mcpInheritance: none` means no MCP — use shell/files instead.
34
37
 
35
38
  Run tools. Return output. Never spawn. Never implement complex/feature solutions (implementer agent). Never plan (thinker agent).
36
39
  May write simple structural folders/files (scaffold/mkdir/touch/boilerplate only). Not feature solutions.
37
40
  Low-reason write: mechanical/repetitive/obvious; volume OK.
41
+ Scratch/tmp → `{workspaceRoot}/.tmp/grunt/` only (mkdir as needed). Never `.tmp/` root, `.tmp/plans/`, `.tmp/grunt/handoffs/`, or `.tmp/orchestrator-logs/`. Repo scaffold/mkdir/touch/boilerplate stays in the real tree.
38
42
 
39
43
  `job: search|exec` → first action `node scripts/grunt-job.mjs --job search|exec --query …`; whole reply = stdout.
40
44
  `job: test` → grunt-job; `FALLBACK` (exit 2) → LLM tools.
41
- `job: web` and messy test → LLM grunt (WebSearch/WebFetch).
45
+ `job: web` and messy test → LLM grunt (web_search/web_fetch).
42
46
  Isolation/write reply ≤8 lines. Verdict grammar only — no dumps/recap/HTML/JSON/full logs:
43
47
 
44
48
  ```
@@ -26,6 +26,8 @@ antigravity-cli:
26
26
  inheritMcp: false
27
27
  commandExecutionPolicy: eager
28
28
  tools: [view_file, grep_search, run_command, replace_file_content]
29
+ geminicli:
30
+ model: gemini-2.5-pro
29
31
  ---
30
32
  Voice: `.rulesync/reference/output.md` — must follow.
31
33
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: orchestrator
3
- description: "Parent spawn supervisor. Always spawn+prompt. Echo [agent]: only. No parent search/write except /parent."
3
+ description: "Parent spawn supervisor. Always spawn+prompt. Echo [orchestrator]: or child role tag only. No parent search/write except /parent."
4
4
  targets:
5
5
  - claudecode
6
6
  - grokcli
@@ -29,10 +29,12 @@ antigravity-cli:
29
29
  inheritMcp: false
30
30
  commandExecutionPolicy: eager
31
31
  tools: [view_file, grep_search, run_command]
32
+ geminicli:
33
+ model: gemini-2.5-flash
32
34
  ---
33
35
  Voice: `.rulesync/reference/output.md` — must follow.
34
36
  Protocol: `.rulesync/reference/cascade.md` (peek/kill table need:/resume). Do not paste. Do not open first.
35
- You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = `[agent]:` + child output only.
37
+ You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = `[orchestrator]:` or child role tag + child output only.
36
38
  Spawn only `grunt` | `implementer` | `thinker`. Omit `model`. Isolation `none` unless asked. Never spawn `orchestrator`. Children never spawn.
37
39
  grunt ← tools. World fact → grunt `job: web`. Never memory. implementer ← specified solution (not the spec). thinker ← plan/deep reason/spec.
38
40
  Child prompt = `You are {agent} subagent.` + task + abs paths + verdicts only.
@@ -26,6 +26,8 @@ antigravity-cli:
26
26
  inheritMcp: false
27
27
  commandExecutionPolicy: eager
28
28
  tools: [view_file, grep_search]
29
+ geminicli:
30
+ model: gemini-2.5-pro
29
31
  ---
30
32
  Voice: `.rulesync/reference/output.md` — must follow.
31
33
 
package/AGENTS.md CHANGED
@@ -1,8 +1,9 @@
1
1
  Voice: `.rulesync/reference/output.md` once.
2
+ All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
2
3
  Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
3
4
  Parent = orchestrator. You do not talk. Spawn wait echo.
4
5
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
5
- First token = spawn. No try-then-spawn. No parent probe. After child: `[agent]:` echo only. No skip-spawn. /parent = one-turn.
6
+ First token = spawn. No try-then-spawn. No parent probe. After child: `[orchestrator]:` (or child role tag) echo only. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`, recap `[handoff]:`.
6
7
  grunt ← tools (facts/search/exec/git/web/test/low-reason mechanical write; world fact: `job: web` never memory). implementer ← write defined solution. thinker ← plan/deep reason.
7
8
  Child prompt = `You are {agent} subagent.` + task + abs paths + verdicts only.
8
9
  Peek every 60s/child; quote real host fields; `done|alive|stuck`; no invent; no auto-kill. `timeout_ms=60000` every peek. GAP: block on spawn return = `done`. Stuck/blocked: quote host fields only.
package/CLAUDE.md CHANGED
@@ -1,8 +1,9 @@
1
1
  Voice: `.rulesync/reference/output.md`
2
+ All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
2
3
  Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
3
4
  Parent = this file not `.claude/agents/orchestrator.md`. You do not talk. Spawn wait echo.
4
5
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
5
- First token = spawn. No try-then-spawn. No parent probe. After child: `[agent]:` echo only. No skip-spawn. /parent = one-turn.
6
+ First token = spawn. No try-then-spawn. No parent probe. After child: `[orchestrator]:` (or child role tag) echo only. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`, recap `[handoff]:`.
6
7
  grunt ← tools (facts/search/exec/git/web/test/low-reason mechanical write; world fact: `job: web` never memory). implementer ← write defined solution. thinker ← plan/deep reason.
7
8
  Child prompt = `You are {agent} subagent.` + task + abs paths + verdicts only.
8
9
  Peek every 60s/child; quote real host fields; `done|alive|stuck`; no invent; no auto-kill. `timeout_ms=60000` every peek. GAP: block on spawn return = `done`. Stuck/blocked: quote host fields only.
package/README.md CHANGED
@@ -17,7 +17,7 @@ Contributors to this repo only: local `npm i && npm test`.
17
17
 
18
18
  # GOAL
19
19
 
20
- - synced configs across all 4/5 major cli providers (grok build, claude code, codex, gemini cli, antigravity)! Emit matrix: grok/claude/codex/antigravity (`rulesync generate`). Gemini CLI is a tracked gap (not in generate).
20
+ - synced configs across all 4/5 major cli providers (grok build, claude code, codex, gemini cli, antigravity)! Emit matrix: grok/claude/codex/antigravity (`rulesync generate`) then `emit-mcp-policy` + `emit-gemini` (`GEMINI.md`, `.gemini/agents/{id}/agent.md`; MCP `.gemini/settings.json`) + `emit-agent-shell-tools` (Claude grunt body `Bash`; other hosts `run_terminal_command`). No `-t geminicli`.
21
21
  - max situational speed relative to task intensity
22
22
  - max free token savings:
23
23
  maximally superterse without loosing value - sacrifice grammar for sake of concision
@@ -27,7 +27,7 @@ Contributors to this repo only: local `npm i && npm test`.
27
27
  context engineering
28
28
  - max reasoning and effort where needed and minimal reasoning and effort where not needed
29
29
  - agents:
30
- - orchestrator (low model, low effort) -> always spawn+prompt; never user-facing tokens except `[agent]:` echo. `/parent` one-turn only.
30
+ - orchestrator (low model, low effort) -> always spawn+prompt; never user-facing tokens except `[orchestrator]:` (or child role tag) echo. `/parent` one-turn only; `/handoff` writes `.tmp/grunt/handoffs/{serial}-{slug}-{stamp}.md` and hands the session to a fresh one.
31
31
  - subagents:
32
32
  - grunt (low model, low effort) -> tools. facts/search/exec/git/web/test/low-reason mechanical write. never feature solution. never spawn.
33
33
  - implementer (medium model, medium effort) -> write already-defined solution. does not plan or invent design. never spawn.
package/cli/init.mjs CHANGED
@@ -7,10 +7,14 @@ const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
7
7
 
8
8
  const PRODUCT_SCRIPTS = [
9
9
  "check-globals.mjs",
10
+ "emit-agent-shell-tools.mjs",
11
+ "emit-gemini.mjs",
10
12
  "emit-mcp-policy.mjs",
11
13
  "gate-fat-tools.mjs",
14
+ "hooks-union.mjs",
12
15
  "grunt-job.mjs",
13
16
  "parse-need.mjs",
17
+ "persist-handoff.mjs",
14
18
  "persist-plan.mjs",
15
19
  "purge-global-mcps.mjs",
16
20
  "scrub-spawn-prompt.mjs",
@@ -21,7 +25,13 @@ const PRODUCT_SCRIPTS = [
21
25
  ]
22
26
 
23
27
  const COPY_DIRS = [".rulesync", ".grok", ".codex", ".claude", ".agents"]
24
- const COPY_FILES = ["AGENTS.md", "CLAUDE.md", ".mcp.json"]
28
+ const GUARDED_MD_FILES = ["AGENTS.md", "CLAUDE.md"]
29
+ // .mcp.json is intentionally not copied here: scripts/emit-mcp-policy.mjs
30
+ // (run via `rulesync:generate`, see mergeClaudeSettings ordering) owns and
31
+ // merges it, so a plain-copy would just be clobbered/redundant.
32
+ const OWNED_HOOK_FILES = ["scrub-spawn-prompt.mjs", "gate-fat-tools.mjs", "orchestrate-parent.js"]
33
+ const SENTINEL_BEGIN = "<!-- grunt:begin -->"
34
+ const SENTINEL_END = "<!-- grunt:end -->"
25
35
 
26
36
  function sortKeys(obj) {
27
37
  return Object.fromEntries(Object.keys(obj).sort().map((k) => [k, obj[k]]))
@@ -48,6 +58,87 @@ export function mergeGitignore(dest) {
48
58
  fs.appendFileSync(gi, `${prefix}.tmp/\n`)
49
59
  }
50
60
 
61
+ export function mergeGuardedMarkdown(dest, pkgRoot, file) {
62
+ const src = path.join(pkgRoot, file)
63
+ const d = path.join(dest, file)
64
+ if (samePath(src, d)) return
65
+ const srcText = fs.readFileSync(src, "utf8")
66
+
67
+ if (!fs.existsSync(d)) {
68
+ fs.writeFileSync(d, `${SENTINEL_BEGIN}\n${srcText}\n${SENTINEL_END}\n`)
69
+ return
70
+ }
71
+
72
+ const destText = fs.readFileSync(d, "utf8")
73
+ const hasSentinel = [SENTINEL_BEGIN, SENTINEL_END].every((marker) => destText.includes(marker))
74
+
75
+ if (!hasSentinel) {
76
+ const gruntFile = file.replace(/\.md$/, ".grunt.md")
77
+ fs.writeFileSync(path.join(dest, gruntFile), srcText)
78
+ console.log(`${file} exists without grunt sentinel; wrote ${gruntFile} instead (original untouched)`)
79
+ return
80
+ }
81
+
82
+ const beginIdx = destText.indexOf(SENTINEL_BEGIN)
83
+ const endIdx = destText.indexOf(SENTINEL_END)
84
+ const before = destText.slice(0, beginIdx)
85
+ const after = destText.slice(endIdx + SENTINEL_END.length)
86
+ fs.writeFileSync(d, `${before}${SENTINEL_BEGIN}\n${srcText}\n${SENTINEL_END}${after}`)
87
+ }
88
+
89
+ export function mergeClaudeSettings(destRoot, pkgRoot) {
90
+ const srcPath = path.join(pkgRoot, ".claude", "settings.json")
91
+ const destPath = path.join(destRoot, ".claude", "settings.json")
92
+
93
+ if (!fs.existsSync(destPath)) {
94
+ fs.mkdirSync(path.dirname(destPath), { recursive: true })
95
+ fs.copyFileSync(srcPath, destPath)
96
+ return
97
+ }
98
+
99
+ let destSettings
100
+ try {
101
+ destSettings = JSON.parse(fs.readFileSync(destPath, "utf8"))
102
+ } catch (err) {
103
+ throw new Error(`malformed JSON in ${destPath}: ${err.message}`)
104
+ }
105
+
106
+ const srcSettings = JSON.parse(fs.readFileSync(srcPath, "utf8"))
107
+ const isOwnedGroup = (group) =>
108
+ group.hooks.every((entry) => OWNED_HOOK_FILES.some((f) => entry.command.includes(f)))
109
+
110
+ const destHooks = { ...(destSettings.hooks || {}) }
111
+ for (const [event, srcGroups] of Object.entries(srcSettings.hooks)) {
112
+ const destGroups = destHooks[event] || []
113
+ destHooks[event] = [...destGroups.filter((group) => !isOwnedGroup(group)), ...srcGroups]
114
+ }
115
+ destSettings.hooks = destHooks
116
+
117
+ const destPerms = { ...(destSettings.permissions || {}) }
118
+ for (const key of ["deny", "allow"]) {
119
+ const destList = destPerms[key] || []
120
+ const srcList = srcSettings.permissions[key] || []
121
+ const seen = new Set(destList)
122
+ const merged = [...destList]
123
+ for (const item of srcList) {
124
+ if (!seen.has(item)) {
125
+ seen.add(item)
126
+ merged.push(item)
127
+ }
128
+ }
129
+ destPerms[key] = merged
130
+ }
131
+ destSettings.permissions = destPerms
132
+
133
+ for (const key of Object.keys(srcSettings)) {
134
+ if (key.toLowerCase().includes("mcp")) {
135
+ destSettings[key] = srcSettings[key]
136
+ }
137
+ }
138
+
139
+ fs.writeFileSync(destPath, `${JSON.stringify(destSettings, null, 2)}\n`)
140
+ }
141
+
51
142
  export function mergePackageJson(dest, pkgRoot) {
52
143
  const destPath = path.join(dest, "package.json")
53
144
  let destPkg = {}
@@ -78,13 +169,21 @@ export function init(dest, { pkgRoot: pkgRootOpt, execFileSync: exec = execFileS
78
169
  const src = path.join(pkgRoot, dir)
79
170
  const d = path.join(dest, dir)
80
171
  fs.mkdirSync(d, { recursive: true })
81
- if (!samePath(src, d)) fs.cpSync(src, d, { recursive: true, force: true })
172
+ if (samePath(src, d)) continue
173
+ if (dir === ".claude") {
174
+ fs.cpSync(src, d, {
175
+ recursive: true,
176
+ force: true,
177
+ filter: (s) => path.basename(s) !== "settings.json",
178
+ })
179
+ mergeClaudeSettings(dest, pkgRoot)
180
+ } else {
181
+ fs.cpSync(src, d, { recursive: true, force: true })
182
+ }
82
183
  }
83
184
 
84
- for (const file of COPY_FILES) {
85
- const src = path.join(pkgRoot, file)
86
- const d = path.join(dest, file)
87
- if (!samePath(src, d)) fs.copyFileSync(src, d)
185
+ for (const file of GUARDED_MD_FILES) {
186
+ mergeGuardedMarkdown(dest, pkgRoot, file)
88
187
  }
89
188
 
90
189
  fs.mkdirSync(path.join(dest, "scripts"), { recursive: true })