@lovrozagar/grunt 0.3.4 → 0.3.6

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.
@@ -17,6 +17,7 @@ Voice: `.rulesync/reference/output.md` — must follow.
17
17
  Implement specified solution. Never spawn. Never run for simple tool usage (grunt agent). Never plan (thinker agent).
18
18
  After write: validate+sim (mandatory). Fix hard failures. Return findings. Never skip validation
19
19
  Read/Grep/list_dir only if needed for edit. Prefer: implement the spec; dump via `need:`.
20
+ Write-allowlist = paths listed in the spec/plan only. No unsolicited README/docs/examples. Missing path → blocker/`need:`; do not invent. Checkbox-flip the plan file.
20
21
  TDD when the spec/plan says tests: you write intelligent test, confirm test fails, implement solution, confirm test pass and iterate until completion (red/green/refactor).
21
22
  Return blockers, problems, edge cases, performance, bugs, warnings, inefficiencies / missed optimizations found while implementing or testing.
22
23
 
@@ -12,11 +12,14 @@ commandExecutionPolicy: eager
12
12
  inheritMcp: false
13
13
  ---
14
14
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
15
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + **one-line** echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
15
16
  Protocol: `.rulesync/reference/cascade.md` (peek/kill table need:/resume). Do not paste. Do not open first.
16
17
  Always-do:
17
18
 
18
19
  | signal | next |
19
20
  |---|---|
21
+ | create/change product files | spawn implementer (thinker then implementer if no spec). Never parent Write/Bash/Skill |
22
+ | plan/spec ready + file writes remain | persist plan if needed; spawn implementer; do not recap-stop |
20
23
  | tools/facts/git/test/mechanical write | spawn grunt |
21
24
  | world fact | spawn grunt `job:web` |
22
25
  | write defined solution | spawn implementer |
@@ -25,11 +28,12 @@ Always-do:
25
28
  | child `need:` | parse-need + parallel grunt; `resume_from` + new verdicts; max 3 |
26
29
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
27
30
  | work remains | spawn; do not stop |
28
- | children done + no findings | recap; stop allowed |
31
+ | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
29
32
  | `/parent` | one-turn parent tools |
30
33
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
34
+ | `/write-plan` plan-only | recap + `next: /implement-plan` |
31
35
 
32
- You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + echo only. Siblings still run: `[grunt]:` echo. Illegal: `[grunt done]` `[[agent] done]` wait-prose.
36
+ You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + **one-line** echo only. Siblings still run: `[grunt]:` echo. Facts/search/trees → grunt, never thinker.
33
37
  Spawn only `grunt` | `implementer` | `thinker`. Omit `model`. Isolation `none` unless asked. Never spawn `orchestrator`. Children never spawn.
34
38
  grunt ← tools. World fact → grunt `job: web`. Never memory. implementer ← specified solution (not the spec). thinker ← plan/deep reason/spec.
35
39
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
@@ -3,7 +3,6 @@ name: thinker
3
3
  description: Plan/deep reason. Edge cases/pitfalls. Read-only. Never spawn. No bash.
4
4
  tools:
5
5
  - view_file
6
- - grep_search
7
6
  mainAgent: false
8
7
  subagent: true
9
8
  model: pro
@@ -13,7 +12,7 @@ inheritMcp: false
13
12
  Voice: `.rulesync/reference/output.md` — must follow.
14
13
 
15
14
  Plan the solution. Deep reason. Never spawn. Never implement (implementer agent). Never run for simple tool usage (grunt agent).
16
- Read-only. No bash. Tiny Read/Grep/list_dir only if needed to plan. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
15
+ Read-only. No bash. Named-file Read of SSOT paths in the prompt only; investigate/search/trees → immediate `need:` for grunt. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
17
16
  Return the spec/plan. Flag edge cases/pitfalls.
18
17
 
19
18
  Fat dump → stop on this JSON only (≤4/batch; no serial known-parallel):
@@ -20,6 +20,16 @@
20
20
  "timeout": 5
21
21
  }
22
22
  ]
23
+ },
24
+ {
25
+ "matcher": "Write|Edit|write|search_replace|Bash|run_terminal_command|Skill|skill|replace_file_content|run_command",
26
+ "hooks": [
27
+ {
28
+ "type": "command",
29
+ "command": "node \"${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/.grok/hooks/orchestrate-parent.js\"",
30
+ "timeout": 5
31
+ }
32
+ ]
23
33
  }
24
34
  ],
25
35
  "Stop": [
@@ -20,6 +20,7 @@ Voice: `.rulesync/reference/output.md` — must follow.
20
20
  Implement specified solution. Never spawn. Never run for simple tool usage (grunt agent). Never plan (thinker agent).
21
21
  After write: validate+sim (mandatory). Fix hard failures. Return findings. Never skip validation
22
22
  Read/Grep/list_dir only if needed for edit. Prefer: implement the spec; dump via `need:`.
23
+ Write-allowlist = paths listed in the spec/plan only. No unsolicited README/docs/examples. Missing path → blocker/`need:`; do not invent. Checkbox-flip the plan file.
23
24
  TDD when the spec/plan says tests: you write intelligent test, confirm test fails, implement solution, confirm test pass and iterate until completion (red/green/refactor).
24
25
  Return blockers, problems, edge cases, performance, bugs, warnings, inefficiencies / missed optimizations found while implementing or testing.
25
26
 
@@ -15,11 +15,14 @@ permissionMode: bypassPermissions
15
15
  effort: low
16
16
  ---
17
17
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
18
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + **one-line** echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
18
19
  Protocol: `.rulesync/reference/cascade.md` (peek/kill table need:/resume). Do not paste. Do not open first.
19
20
  Always-do:
20
21
 
21
22
  | signal | next |
22
23
  |---|---|
24
+ | create/change product files | spawn implementer (thinker then implementer if no spec). Never parent Write/Bash/Skill |
25
+ | plan/spec ready + file writes remain | persist plan if needed; spawn implementer; do not recap-stop |
23
26
  | tools/facts/git/test/mechanical write | spawn grunt |
24
27
  | world fact | spawn grunt `job:web` |
25
28
  | write defined solution | spawn implementer |
@@ -28,11 +31,12 @@ Always-do:
28
31
  | child `need:` | parse-need + parallel grunt; `resume_from` + new verdicts; max 3 |
29
32
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
30
33
  | work remains | spawn; do not stop |
31
- | children done + no findings | recap; stop allowed |
34
+ | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
32
35
  | `/parent` | one-turn parent tools |
33
36
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
37
+ | `/write-plan` plan-only | recap + `next: /implement-plan` |
34
38
 
35
- You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + echo only. Siblings still run: `[grunt]:` echo. Illegal: `[grunt done]` `[[agent] done]` wait-prose.
39
+ You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + **one-line** echo only. Siblings still run: `[grunt]:` echo. Facts/search/trees → grunt, never thinker.
36
40
  Spawn only `grunt` | `implementer` | `thinker`. Omit `model`. Isolation `none` unless asked. Never spawn `orchestrator`. Children never spawn.
37
41
  grunt ← tools. World fact → grunt `job: web`. Never memory. implementer ← specified solution (not the spec). thinker ← plan/deep reason/spec.
38
42
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
@@ -4,20 +4,20 @@ description: Plan/deep reason. Edge cases/pitfalls. Read-only. Never spawn. No b
4
4
  model: opus
5
5
  tools:
6
6
  - Read
7
- - Grep
8
- - Glob
9
7
  disallowedTools:
10
8
  - Agent
11
9
  - Write
12
10
  - Edit
13
11
  - Bash
12
+ - Grep
13
+ - Glob
14
14
  permissionMode: bypassPermissions
15
15
  effort: high
16
16
  ---
17
17
  Voice: `.rulesync/reference/output.md` — must follow.
18
18
 
19
19
  Plan the solution. Deep reason. Never spawn. Never implement (implementer agent). Never run for simple tool usage (grunt agent).
20
- Read-only. No bash. Tiny Read/Grep/list_dir only if needed to plan. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
20
+ Read-only. No bash. Named-file Read of SSOT paths in the prompt only; investigate/search/trees → immediate `need:` for grunt. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
21
21
  Return the spec/plan. Flag edge cases/pitfalls.
22
22
 
23
23
  Fat dump → stop on this JSON only (≤4/batch; no serial known-parallel):
@@ -29,6 +29,16 @@
29
29
  "timeout": 5
30
30
  }
31
31
  ]
32
+ },
33
+ {
34
+ "matcher": "Write|Edit|write|search_replace|Bash|run_terminal_command|Skill|skill|replace_file_content|run_command",
35
+ "hooks": [
36
+ {
37
+ "type": "command",
38
+ "command": "node \"${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/.grok/hooks/orchestrate-parent.js\"",
39
+ "timeout": 5
40
+ }
41
+ ]
32
42
  }
33
43
  ],
34
44
  "UserPromptSubmit": [
@@ -10,6 +10,7 @@ Voice: `.rulesync/reference/output.md` — must follow.
10
10
  Implement specified solution. Never spawn. Never run for simple tool usage (grunt agent). Never plan (thinker agent).
11
11
  After write: validate+sim (mandatory). Fix hard failures. Return findings. Never skip validation
12
12
  Read/Grep/list_dir only if needed for edit. Prefer: implement the spec; dump via `need:`.
13
+ Write-allowlist = paths listed in the spec/plan only. No unsolicited README/docs/examples. Missing path → blocker/`need:`; do not invent. Checkbox-flip the plan file.
13
14
  TDD when the spec/plan says tests: you write intelligent test, confirm test fails, implement solution, confirm test pass and iterate until completion (red/green/refactor).
14
15
  Return blockers, problems, edge cases, performance, bugs, warnings, inefficiencies / missed optimizations found while implementing or testing.
15
16
 
@@ -6,11 +6,14 @@ sandbox_mode = "danger-full-access"
6
6
  approval_policy = "never"
7
7
  developer_instructions = '''
8
8
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
9
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + **one-line** echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
9
10
  Protocol: `.rulesync/reference/cascade.md` (peek/kill table need:/resume). Do not paste. Do not open first.
10
11
  Always-do:
11
12
 
12
13
  | signal | next |
13
14
  |---|---|
15
+ | create/change product files | spawn implementer (thinker then implementer if no spec). Never parent Write/Bash/Skill |
16
+ | plan/spec ready + file writes remain | persist plan if needed; spawn implementer; do not recap-stop |
14
17
  | tools/facts/git/test/mechanical write | spawn grunt |
15
18
  | world fact | spawn grunt `job:web` |
16
19
  | write defined solution | spawn implementer |
@@ -19,11 +22,12 @@ Always-do:
19
22
  | child `need:` | parse-need + parallel grunt; `resume_from` + new verdicts; max 3 |
20
23
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
21
24
  | work remains | spawn; do not stop |
22
- | children done + no findings | recap; stop allowed |
25
+ | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
23
26
  | `/parent` | one-turn parent tools |
24
27
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
28
+ | `/write-plan` plan-only | recap + `next: /implement-plan` |
25
29
 
26
- You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + echo only. Siblings still run: `[grunt]:` echo. Illegal: `[grunt done]` `[[agent] done]` wait-prose.
30
+ You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + **one-line** echo only. Siblings still run: `[grunt]:` echo. Facts/search/trees → grunt, never thinker.
27
31
  Spawn only `grunt` | `implementer` | `thinker`. Omit `model`. Isolation `none` unless asked. Never spawn `orchestrator`. Children never spawn.
28
32
  grunt ← tools. World fact → grunt `job: web`. Never memory. implementer ← specified solution (not the spec). thinker ← plan/deep reason/spec.
29
33
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
@@ -8,7 +8,7 @@ developer_instructions = '''
8
8
  Voice: `.rulesync/reference/output.md` — must follow.
9
9
 
10
10
  Plan the solution. Deep reason. Never spawn. Never implement (implementer agent). Never run for simple tool usage (grunt agent).
11
- Read-only. No bash. Tiny Read/Grep/list_dir only if needed to plan. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
11
+ Read-only. No bash. Named-file Read of SSOT paths in the prompt only; investigate/search/trees → immediate `need:` for grunt. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
12
12
  Return the spec/plan. Flag edge cases/pitfalls.
13
13
 
14
14
  Fat dump → stop on this JSON only (≤4/batch; no serial known-parallel):
package/.codex/hooks.json CHANGED
@@ -20,6 +20,16 @@
20
20
  "timeout": 5
21
21
  }
22
22
  ]
23
+ },
24
+ {
25
+ "matcher": "Write|Edit|write|search_replace|Bash|run_terminal_command|Skill|skill|replace_file_content|run_command",
26
+ "hooks": [
27
+ {
28
+ "type": "command",
29
+ "command": "node \"${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/.grok/hooks/orchestrate-parent.js\"",
30
+ "timeout": 5
31
+ }
32
+ ]
23
33
  }
24
34
  ],
25
35
  "UserPromptSubmit": [
@@ -12,6 +12,7 @@ Voice: `.rulesync/reference/output.md` — must follow.
12
12
  Implement specified solution. Never spawn. Never run for simple tool usage (grunt agent). Never plan (thinker agent).
13
13
  After write: validate+sim (mandatory). Fix hard failures. Return findings. Never skip validation
14
14
  Read/Grep/list_dir only if needed for edit. Prefer: implement the spec; dump via `need:`.
15
+ Write-allowlist = paths listed in the spec/plan only. No unsolicited README/docs/examples. Missing path → blocker/`need:`; do not invent. Checkbox-flip the plan file.
15
16
  TDD when the spec/plan says tests: you write intelligent test, confirm test fails, implement solution, confirm test pass and iterate until completion (red/green/refactor).
16
17
  Return blockers, problems, edge cases, performance, bugs, warnings, inefficiencies / missed optimizations found while implementing or testing.
17
18
 
@@ -8,11 +8,14 @@ mcpInheritance: none
8
8
  tools: spawn_subagent, read_file, grep, list_dir, write, todo_write, get_command_or_subagent_output, kill_command_or_subagent, run_terminal_command
9
9
  ---
10
10
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
11
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + **one-line** echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
11
12
  Protocol: `.rulesync/reference/cascade.md` (peek/kill table need:/resume). Do not paste. Do not open first.
12
13
  Always-do:
13
14
 
14
15
  | signal | next |
15
16
  |---|---|
17
+ | create/change product files | spawn implementer (thinker then implementer if no spec). Never parent Write/Bash/Skill |
18
+ | plan/spec ready + file writes remain | persist plan if needed; spawn implementer; do not recap-stop |
16
19
  | tools/facts/git/test/mechanical write | spawn grunt |
17
20
  | world fact | spawn grunt `job:web` |
18
21
  | write defined solution | spawn implementer |
@@ -21,11 +24,12 @@ Always-do:
21
24
  | child `need:` | parse-need + parallel grunt; `resume_from` + new verdicts; max 3 |
22
25
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
23
26
  | work remains | spawn; do not stop |
24
- | children done + no findings | recap; stop allowed |
27
+ | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
25
28
  | `/parent` | one-turn parent tools |
26
29
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
30
+ | `/write-plan` plan-only | recap + `next: /implement-plan` |
27
31
 
28
- You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + echo only. Siblings still run: `[grunt]:` echo. Illegal: `[grunt done]` `[[agent] done]` wait-prose.
32
+ You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + **one-line** echo only. Siblings still run: `[grunt]:` echo. Facts/search/trees → grunt, never thinker.
29
33
  Spawn only `grunt` | `implementer` | `thinker`. Omit `model`. Isolation `none` unless asked. Never spawn `orchestrator`. Children never spawn.
30
34
  grunt ← tools. World fact → grunt `job: web`. Never memory. implementer ← specified solution (not the spec). thinker ← plan/deep reason/spec.
31
35
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
@@ -5,12 +5,12 @@ model: grok-4.6
5
5
  agents_md: false
6
6
  mcpInheritance: none
7
7
  permission_mode: bypassPermissions
8
- tools: read_file, grep, list_dir
8
+ tools: read_file
9
9
  ---
10
10
  Voice: `.rulesync/reference/output.md` — must follow.
11
11
 
12
12
  Plan the solution. Deep reason. Never spawn. Never implement (implementer agent). Never run for simple tool usage (grunt agent).
13
- Read-only. No bash. Tiny Read/Grep/list_dir only if needed to plan. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
13
+ Read-only. No bash. Named-file Read of SSOT paths in the prompt only; investigate/search/trees → immediate `need:` for grunt. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
14
14
  Return the spec/plan. Flag edge cases/pitfalls.
15
15
 
16
16
  Fat dump → stop on this JSON only (≤4/batch; no serial known-parallel):
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /** Parent-orchestrator gate: spawn/peek/kill/todo + persistPlan/persistHandoff writes.
2
+ /** PreToolUse parent-deny on Claude+Grok+Antigravity; Stop = first-non-empty-line recap; stop-block survives host banners; SubagentStop is single-registration intercept.
3
3
  Parent Read/Grep/Glob/Bash/Web denied unless parent-escape (fat-gate still).
4
4
  SubagentStop intercepts need: search|exec with grunt-job verdicts.
5
5
  Stop: first-non-empty-line [orchestrator]:/[grunt]:/[implementer]:/[thinker]:/[handoff]: recap
@@ -19,6 +19,7 @@ import {
19
19
  fatHookOutput,
20
20
  fileSizeBytes,
21
21
  isWorkspaceGruntJob,
22
+ isParentOrchestrator,
22
23
  processFatTools,
23
24
  resolveReadPath,
24
25
  rewriteGruntScratchPath,
@@ -30,23 +31,11 @@ import { parseNeed } from "../../scripts/parse-need.mjs";
30
31
  import { resolveJobCwd, runJob } from "../../scripts/grunt-job.mjs";
31
32
  import { logTelemetry, ORCHESTRATOR_LOGS_DIR } from "../../scripts/telemetry.mjs";
32
33
 
33
- const DENY_REASON = "parent is orchestrator; spawn grunt|implementer|thinker";
34
+ export const DENY_REASON = "spawn implementer|grunt|thinker";
34
35
  export const STOP_REASONS = [
35
- "Violation: Stop is not a recap-only wall. XOR one action now. DO NOT stop.\n" +
36
- "⚠/validate/sim findings → spawn implementer with them; do not recap; spawn; no parent-edit.\n" +
37
- "Else work remains → spawn grunt|implementer|thinker; do not glue done into the tag.\n" +
38
- "Else all children returned and no findings → recap `[orchestrator]:` echo.\n" +
39
- "Illegal: `[grunt done]` `[[agent] done]`.",
40
- "Second violation: still no exclusive next action. XOR one. DO NOT stop.\n" +
41
- "⚠/validate/sim findings → spawn implementer with them; do not recap; spawn; no parent-edit.\n" +
42
- "Else work remains → spawn grunt|implementer|thinker; do not glue done into the tag.\n" +
43
- "Else all children returned and no findings → recap `[orchestrator]:` echo.\n" +
44
- "Illegal: `[grunt done]` `[[agent] done]`.",
45
- "Third violation: last check before fail-open. XOR one action. DO NOT stop.\n" +
46
- "⚠/validate/sim findings → spawn implementer with them; do not recap; spawn; no parent-edit.\n" +
47
- "Else work remains → spawn grunt|implementer|thinker; do not glue done into the tag.\n" +
48
- "Else all children returned and no findings → recap `[orchestrator]:` echo.\n" +
49
- "Illegal: `[grunt done]` `[[agent] done]`.",
36
+ "Spawn: ⚠/validate/sim findings or writes remain or parent just tried to Write → spawn implementer. Else facts → grunt. Else no spec → thinker. Else recap `[orchestrator]:` one-line.",
37
+ "Still spawn: ⚠/validate/sim findings or writes remain or parent just tried to Write → spawn implementer. Else facts → grunt. Else no spec → thinker. Else recap `[orchestrator]:` one-line.",
38
+ "Last spawn: ⚠/validate/sim findings or writes remain or parent just tried to Write → spawn implementer. Else facts → grunt. Else no spec → thinker. Else recap `[orchestrator]:` one-line.",
50
39
  ];
51
40
  const TRANSCRIPT_TAIL_BYTES = 512 * 1024;
52
41
  const PARENT_TOOLS = new Set([
@@ -62,7 +51,23 @@ const SPAWN_TOOLS = new Set([
62
51
  "agent",
63
52
  "spawnagent",
64
53
  ]);
65
- const WRITE_TOOLS = new Set(["write"]);
54
+ const WRITE_TOOLS = new Set([
55
+ "write",
56
+ "edit",
57
+ "searchreplace",
58
+ "replacefilecontent",
59
+ ]);
60
+ const BASH_TOOLS = new Set(["bash", "runterminalcommand", "runcommand"]);
61
+ const SKILL_TOOLS = new Set(["skill"]);
62
+ const PARENT_SKILLS = new Set([
63
+ "parent",
64
+ "explain",
65
+ "solo",
66
+ "cascade",
67
+ "handoff",
68
+ "write-plan",
69
+ "implement-plan",
70
+ ]);
66
71
  const READ_TOOLS = new Set(["readfile", "read"]);
67
72
  const INTERCEPT_JOBS = new Set(["search", "exec"]);
68
73
  const MAX_STOP = 3;
@@ -100,7 +105,7 @@ function preToolUse(data) {
100
105
  logPreTool(data, toolKey, toolInput);
101
106
 
102
107
  const sub = subagentTypeOf(data);
103
- if (sub) {
108
+ if (sub || !isParentOrchestrator(data)) {
104
109
  const code = emitFat(data);
105
110
  return code == null ? 0 : code;
106
111
  }
@@ -133,6 +138,14 @@ function preToolUse(data) {
133
138
  emit({ decision: "allow" });
134
139
  return 0;
135
140
  }
141
+ if (BASH_TOOLS.has(toolKey) && isAllowedParentGruntJob(bashCommandOf(toolInput), workspaceRootOf(data))) {
142
+ emit({ decision: "allow" });
143
+ return 0;
144
+ }
145
+ if (SKILL_TOOLS.has(toolKey) && isAllowedParentSkill(toolInput)) {
146
+ emit({ decision: "allow" });
147
+ return 0;
148
+ }
136
149
  if (hasParentEscape(data)) {
137
150
  const fatCode = emitFat(data);
138
151
  if (fatCode !== null) return fatCode;
@@ -222,6 +235,40 @@ export function isAllowedParentGruntJob(command, workspaceRoot) {
222
235
  return isWorkspaceGruntJob(command, workspaceRoot, ["search", "exec"]);
223
236
  }
224
237
 
238
+ function bashCommandOf(toolInput) {
239
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
240
+ return "";
241
+ }
242
+ return String(toolInput.command ?? toolInput.cmd ?? "");
243
+ }
244
+
245
+ function skillNameOf(toolInput) {
246
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
247
+ return "";
248
+ }
249
+ const raw =
250
+ toolInput.skill ??
251
+ toolInput.skill_name ??
252
+ toolInput.skillName ??
253
+ toolInput.name ??
254
+ "";
255
+ const s = String(raw).trim().toLowerCase().replace(/\\/g, "/");
256
+ const base = s.split("/").pop() || "";
257
+ return base.replace(/\.md$/, "").replace(/_/g, "-");
258
+ }
259
+
260
+ export function isAllowedParentSkill(toolInput) {
261
+ return PARENT_SKILLS.has(skillNameOf(toolInput));
262
+ }
263
+
264
+ function isHostStopBanner(prompt) {
265
+ const p = String(prompt || "");
266
+ if (/^\s*Stop hook feedback:/.test(p)) return true;
267
+ if (/Blocked by stop hook/.test(p)) return true;
268
+ if (/task[-_ ]?notification/i.test(p)) return true;
269
+ return false;
270
+ }
271
+
225
272
  function parentWrite(data, toolInput) {
226
273
  if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
227
274
  emit({ decision: "deny", reason: DENY_REASON });
@@ -238,6 +285,10 @@ function parentWrite(data, toolInput) {
238
285
  if (rewritten) rawPath = rewritten;
239
286
  const handoff = isUnderHandoffs(rawPath, ws);
240
287
  if (!handoff && !isUnderPlans(rawPath, ws)) {
288
+ if (hasParentEscape(data)) {
289
+ emit({ decision: "allow" });
290
+ return 0;
291
+ }
241
292
  emit({ decision: "deny", reason: DENY_REASON });
242
293
  return 0;
243
294
  }
@@ -301,7 +352,7 @@ function isParentEscapePrompt(prompt) {
301
352
  function userPromptSubmit(data) {
302
353
  unlinkQuiet(stampPath(data, "tools-used"));
303
354
  const prompt = userPromptOf(data);
304
- if (!/^\s*Stop hook feedback:/.test(prompt)) {
355
+ if (!isHostStopBanner(prompt)) {
305
356
  unlinkQuiet(stampPath(data, "stop-block"));
306
357
  }
307
358
  // Sticky: only /solo and /cascade move it. Every other prompt leaves it alone.
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -55,6 +55,7 @@ You are implementer subagent. Do not spawn.
55
55
  Read {abs}/.grok/skills/shared/plan-format.md and the plan at {abs_plan_path}.
56
56
 
57
57
  Implement remaining `[ ]` leaves in numeric order. Skip `[x]`.
58
+ Write-allowlist = paths listed in this plan only. Do not add README/docs/examples unless listed in the plan. Missing path → blocker/`need:`; do not invent.
58
59
  After each leaf: flip that line to `[x]` in the plan file (only the box).
59
60
  When a phase's children are all `[x]`, mark the phase `[x]`.
60
61
  Frontmatter status: in-progress at start; done when all boxes `[x]`; blocked if you cannot finish.
@@ -39,6 +39,8 @@ Prompt:
39
39
  ```
40
40
  You are thinker subagent. Read-only. Do not write files. Do not spawn.
41
41
 
42
+ Named-file Read of listed abs paths only; investigate/search/trees → `need:` (parent prefetch still allowed).
43
+
42
44
  Read {abs}/.grok/skills/shared/plan-format.md. Produce a plan that matches it.
43
45
 
44
46
  Task:
@@ -12,15 +12,18 @@
12
12
  },
13
13
  {
14
14
  // Fat Read/Grep/Glob/Bash + Write/Edit scratch rewrite (never deny writes).
15
- // Parent-deny stays Grok-only (orchestrate-parent.js).
16
15
  "matcher": "Read|read_file|Grep|grep|Glob|list_dir|Bash|run_terminal_command|Write|Edit|write|search_replace",
17
16
  "type": "command",
18
17
  "command": "node \"${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/scripts/gate-fat-tools.mjs\"",
19
18
  "timeout": 5
19
+ },
20
+ {
21
+ "matcher": "Write|Edit|write|search_replace|Bash|run_terminal_command|Skill|skill|replace_file_content|run_command",
22
+ "type": "command",
23
+ "command": "node \"${GROK_WORKSPACE_ROOT:-${CLAUDE_PROJECT_DIR:-.}}/.grok/hooks/orchestrate-parent.js\"",
24
+ "timeout": 5
20
25
  }
21
26
  ],
22
- // Parent Stop + /parent stamp. PreToolUse parent-deny stays Grok-only
23
- // (child subagentType/agent_type unverified on these hosts).
24
27
  "beforeSubmitPrompt": [
25
28
  {
26
29
  "type": "command",
@@ -13,15 +13,16 @@ Always-do lives in orchestrator/overview (do not open this file first). Peek/kil
13
13
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
14
14
 
15
15
  Always spawn + prompt. First token = spawn. No try-then-spawn. No parent probe. No tiny-read/grep/list_dir/bash/grunt-job for the user. No skip-spawn. `/parent` is the only parent-tool escape.
16
+ Create/change product files → first token spawn implementer (thinker then implementer if no spec). Never parent Write/Edit/Bash/Skill-that-writes. Child returned is not terminal if writes remain or spawn count is 0 on a file-change turn. plan/spec ready + writes remain → persist plan if needed; spawn implementer; do not recap-stop. plan-only `/write-plan` → recap + `next: /implement-plan`.
16
17
 
17
- 1. Tools (facts / search / exec / git / web / test / low-reason mechanical write) → grunt.
18
+ 1. Tools (facts / search / exec / git / web / test / low-reason mechanical write) → grunt. Facts/search → grunt, never thinker.
18
19
  2. Write defined solution → implementer.
19
20
  3. Plan / deep reason (edge cases / pitfalls) → thinker.
20
21
  4. Fresh/world facts → grunt `job: web`. Never answer from memory.
21
22
 
22
23
  Spawn only `grunt` | `implementer` | `thinker`. Omit `model`. Isolation `none` unless asked.
23
24
 
24
- Implementer/thinker may tiny-Read/Grep/list_dir themselves. Fat dumps via `need:` JSON.
25
+ Implementer may tiny-Read/Grep/list_dir. Thinker named-file Read of prompt SSOT only; investigate/search/trees → `need:` grunt. Fat dumps via `need:` JSON.
25
26
 
26
27
  Long parent sessions rely on two-pass compaction; prefer a new parent session per task rather than unbounded resume chains. `[features] two_pass_compaction = true` must live in `~/.grok/config.toml` (from `.grok/global-settings.toml` via `npm run sync:globals` dry-run / `npm run sync:globals:apply`). Project `.grok/config.toml` cannot set `[features]`. Do not add a SessionStart hook.
27
28
 
@@ -34,7 +35,7 @@ Classify `done` | `alive` | `stuck` vs prior peek: **done** (completed/failed),
34
35
  Loop until done. No auto-kill. Kill only if the user asks and the host has a kill tool.
35
36
  Then print recap as `[orchestrator]: …` (or the child role tag) with real child output. Do not redo the child's work.
36
37
 
37
- Parent Stop (`MAX_STOP=3`): do not waive fenced/impl finals for `tools-used` stamp. First non-empty line must match legal `[orchestrator|grunt|implementer|thinker|handoff]:`. Illegal: `[grunt done]` `[[agent] done]` wait-prose. Siblings still run → role tag + echo, not `[grunt done]`. Or consume `parent-escape-{sid}` once. Else block. No `isCheap` / trivia / long definitions.
38
+ Parent Stop (`MAX_STOP=3`): do not waive fenced/impl finals for `tools-used` stamp. Parent `end_turn` is tagged+one-line or `[orchestrator]: wait grunt`. First non-empty line must match legal `[orchestrator|grunt|implementer|thinker|handoff]:`. Siblings still run → role tag + one-line echo. In-flight wait recap is `[orchestrator]: wait grunt` only. Or consume `parent-escape-{sid}` once. Else block. No `isCheap` / trivia / long definitions.
38
39
  `/handoff` is the other in-parent turn: context large + work unfinished → parent writes one `.tmp/grunt/handoffs/` file from its own transcript (no child sees the session), recaps `[handoff]: serial=… path=…`, tells the user to continue in a new session. No spawn, one write, not a mode.
39
40
  `/solo` is the session mode (not one-turn): `beforeSubmitPrompt` writes `.tmp/orchestrator-logs/grunt-off-{sid}`, every gate short-circuits while it exists, `/cascade` unlinks it. Everything above resumes next turn. Antigravity: skill-only, no stamp create (see hooks.md). `/parent` is one-turn (not a mode): UserPromptSubmit writes `.tmp/orchestrator-logs/parent-escape-{sid}`; Stop consumes+unlinks once. Next Stop resumes spawn/recap rules. Skills point here; do not paste this file.
40
41
 
@@ -43,7 +44,7 @@ Host mapping (in-tree only; do not invent peek/kill APIs). GAP rows: no fake pee
43
44
  | Host | Spawn | Peek | Kill |
44
45
  | --- | --- | --- | --- |
45
46
  | Grok | `spawn_subagent` `background:true` → `task_id` | `get_command_or_subagent_output` + `timeout_ms=60000` | `kill_command_or_subagent` (user-ask only) |
46
- | Claude Code | `Agent` if the parent session exposes it | GAP unless an in-tree schema names a status/output tool on that id (do not invent `TaskOutput`); else block on Agent return and classify `done`. Agent launch ≠ child done; in-flight host Stop → only `[orchestrator]: wait grunt` (or role echo); no SendMessage | GAP unless in-tree; no auto-kill |
47
+ | Claude Code | `Agent` if the parent session exposes it | GAP unless an in-tree schema names a status/output tool on that id (do not invent `TaskOutput`); else block on Agent return and classify `done`. Agent launch ≠ child done; in-flight host Stop → only `[orchestrator]: wait grunt`; no SendMessage | GAP unless in-tree; no auto-kill |
47
48
  | Codex | host agent/call | GAP; block on host agent/call return; classify `done` | GAP; no auto-kill |
48
49
  | Antigravity | main-session parent | GAP peek/kill; main-session parent | GAP; no auto-kill |
49
50
  | Gemini | not emitted; tracked gap | GAP; no fake peeks | GAP; no auto-kill |
@@ -10,9 +10,11 @@ Grok PostToolUse / UserPromptSubmit / SessionStart are observe-only — do not a
10
10
 
11
11
  Claude / Codex / Antigravity PreToolUse still run `scripts/scrub-spawn-prompt.mjs` and `scripts/gate-fat-tools.mjs`. Grok does not — spawn/fat rewrite is `orchestrate-parent.js` only. RTK stays `rtk.json`.
12
12
 
13
+ Parent Write|Edit|Bash|Skill-that-writes is deny on Claude+Grok+Antigravity unless `/parent`. `.tmp/plans/` and `.tmp/grunt/handoffs/` Write still persist. Parent Bash still allowed for workspace `grunt-job.mjs` `--job search|exec`. Children with a type skip parent-deny (fat-only). Skill allowlist without escape: `parent|explain|solo|cascade|handoff|write-plan|implement-plan`. Deny reason: `spawn implementer|grunt|thinker`. Parent `end_turn` is tagged+one-line or `[orchestrator]: wait grunt`.
14
+
13
15
  Parent `run_terminal_command` is deny-by-default except `node` (optional leading `rtk`) invoking `{workspace}/scripts/grunt-job.mjs` with `--job search|exec` and `--query`. Optional `--path` `--glob` (repeatable) `--cwd`. Not `web|test`, not other scripts. META strips `--query` value first so `|` inside query is allowed; pipes/`&&` outside query still deny. Never `cd &&`. Exec shell-meta in query is grunt-job FALLBACK (exit 2), not a parent-bash allow. Unknown flags → FALLBACK.
14
16
 
15
- SubagentStop (and child Stop with `subagentType`) intercepts a parseable `need:` whose jobs are all `search|exec` (cap 4): runs grunt-job in-hook and continues the child with concatenated `verdict:` blobs. Mixed/web/test, parse fail, FALLBACK, `stopHookActive`, or more than 3 intercepts per child session → empty stdout (allow stop; parent LLM path). Parent `write` is deny-by-default except `.tmp/plans/` (`persist-plan.mjs`) and `.tmp/grunt/handoffs/` (`persist-handoff.mjs`); both validate + rewrite path/content, invalid body denies. Parent Stop (`MAX_STOP=3`): do not waive impl finals for `tools-used`. First non-empty line must match legal `[orchestrator|grunt|implementer|thinker|handoff]:`. Illegal: `[grunt done]` `[[agent] done]` wait-prose. Claude Agent launch ≠ done; in-flight host Stop → wait recap only (`[orchestrator]: wait grunt` or role echo); no SendMessage. Or consume `parent-escape-{sid}` once (`/parent`). No `isCheap` / trivia. Stamps: `tools-used` / `stop-block` / `need-intercept` / `parent-escape`. Skills point to cascade; do not paste protocol here.
17
+ SubagentStop (and child Stop with `subagentType`) intercepts a parseable `need:` whose jobs are all `search|exec` (cap 4): runs grunt-job in-hook and continues the child with concatenated `verdict:` blobs. Mixed/web/test, parse fail, FALLBACK, `stopHookActive`, or more than 3 intercepts per child session → empty stdout (allow stop; parent LLM path). Parent `write` is deny-by-default except `.tmp/plans/` (`persist-plan.mjs`) and `.tmp/grunt/handoffs/` (`persist-handoff.mjs`); both validate + rewrite path/content, invalid body denies. Parent Stop (`MAX_STOP=3`): do not waive impl finals for `tools-used`. Parent `end_turn` is tagged+one-line or `[orchestrator]: wait grunt`. First non-empty line must match legal `[orchestrator|grunt|implementer|thinker|handoff]:`. Claude Agent launch ≠ done; in-flight host Stop → wait recap `[orchestrator]: wait grunt` only; no SendMessage. Or consume `parent-escape-{sid}` once (`/parent`). No `isCheap` / trivia. Stamps: `tools-used` / `stop-block` / `need-intercept` / `parent-escape`. Skills point to cascade; do not paste protocol here.
16
18
 
17
19
  `/solo` writes `grunt-off-{sid}`; `/cascade` unlinks it (`beforeSubmitPrompt`, exact-match only). While set: PreToolUse allows parent tools (fat gate still runs) and skips the `defaultGrunt` spawn rewrite; Stop returns 0 **before** the `parent-escape` consume, so solo never burns the one-turn stamp. Session-scoped, fail-closed.
18
20
 
@@ -4,4 +4,4 @@ tags: [output]
4
4
 
5
5
  # Output
6
6
 
7
- en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only.
7
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + one-line echo only
@@ -4,12 +4,15 @@ targets:
4
4
  - claudecode
5
5
  ---
6
6
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
7
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + one-line echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
7
8
  All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
8
9
  Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
9
10
  Always-do:
10
11
 
11
12
  | signal | next |
12
13
  |---|---|
14
+ | create/change product files | spawn implementer (thinker then implementer if no spec). Never parent Write/Bash/Skill |
15
+ | plan/spec ready + file writes remain | persist plan if needed; spawn implementer; do not recap-stop |
13
16
  | tools/facts/git/test/mechanical write | spawn grunt |
14
17
  | world fact | spawn grunt `job:web` |
15
18
  | write defined solution | spawn implementer |
@@ -18,13 +21,14 @@ Always-do:
18
21
  | child `need:` | parse-need + parallel grunt; `resume_from` + new verdicts; max 3 |
19
22
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
20
23
  | work remains | spawn; do not stop |
21
- | children done + no findings | recap; stop allowed |
24
+ | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
22
25
  | `/parent` | one-turn parent tools |
23
26
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
27
+ | `/write-plan` plan-only | recap + `next: /implement-plan` |
24
28
 
25
29
  Parent = this file not `.claude/agents/orchestrator.md`. You do not talk. Spawn wait echo.
26
30
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
27
- First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + echo only. Siblings still run: `[grunt]:` echo. Illegal: `[grunt done]` `[[agent] done]` wait-prose. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
31
+ First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
28
32
  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.
29
33
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
30
34
  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,12 +9,15 @@ globs:
9
9
  - "**/*"
10
10
  ---
11
11
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
12
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + one-line echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
12
13
  All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
13
14
  Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
14
15
  Always-do:
15
16
 
16
17
  | signal | next |
17
18
  |---|---|
19
+ | create/change product files | spawn implementer (thinker then implementer if no spec). Never parent Write/Bash/Skill |
20
+ | plan/spec ready + file writes remain | persist plan if needed; spawn implementer; do not recap-stop |
18
21
  | tools/facts/git/test/mechanical write | spawn grunt |
19
22
  | world fact | spawn grunt `job:web` |
20
23
  | write defined solution | spawn implementer |
@@ -23,13 +26,14 @@ Always-do:
23
26
  | child `need:` | parse-need + parallel grunt; `resume_from` + new verdicts; max 3 |
24
27
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
25
28
  | work remains | spawn; do not stop |
26
- | children done + no findings | recap; stop allowed |
29
+ | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
27
30
  | `/parent` | one-turn parent tools |
28
31
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
32
+ | `/write-plan` plan-only | recap + `next: /implement-plan` |
29
33
 
30
34
  Parent = orchestrator. You do not talk. Spawn wait echo.
31
35
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
32
- First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + echo only. Siblings still run: `[grunt]:` echo. Illegal: `[grunt done]` `[[agent] done]` wait-prose. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
36
+ First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
33
37
  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.
34
38
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
35
39
  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.
@@ -34,6 +34,7 @@ Voice: `.rulesync/reference/output.md` — must follow.
34
34
  Implement specified solution. Never spawn. Never run for simple tool usage (grunt agent). Never plan (thinker agent).
35
35
  After write: validate+sim (mandatory). Fix hard failures. Return findings. Never skip validation
36
36
  Read/Grep/list_dir only if needed for edit. Prefer: implement the spec; dump via `need:`.
37
+ Write-allowlist = paths listed in the spec/plan only. No unsolicited README/docs/examples. Missing path → blocker/`need:`; do not invent. Checkbox-flip the plan file.
37
38
  TDD when the spec/plan says tests: you write intelligent test, confirm test fails, implement solution, confirm test pass and iterate until completion (red/green/refactor).
38
39
  Return blockers, problems, edge cases, performance, bugs, warnings, inefficiencies / missed optimizations found while implementing or testing.
39
40
 
@@ -34,11 +34,14 @@ geminicli:
34
34
  model: gemini-2.5-flash
35
35
  ---
36
36
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
37
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + **one-line** echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
37
38
  Protocol: `.rulesync/reference/cascade.md` (peek/kill table need:/resume). Do not paste. Do not open first.
38
39
  Always-do:
39
40
 
40
41
  | signal | next |
41
42
  |---|---|
43
+ | create/change product files | spawn implementer (thinker then implementer if no spec). Never parent Write/Bash/Skill |
44
+ | plan/spec ready + file writes remain | persist plan if needed; spawn implementer; do not recap-stop |
42
45
  | tools/facts/git/test/mechanical write | spawn grunt |
43
46
  | world fact | spawn grunt `job:web` |
44
47
  | write defined solution | spawn implementer |
@@ -47,11 +50,12 @@ Always-do:
47
50
  | child `need:` | parse-need + parallel grunt; `resume_from` + new verdicts; max 3 |
48
51
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
49
52
  | work remains | spawn; do not stop |
50
- | children done + no findings | recap; stop allowed |
53
+ | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
51
54
  | `/parent` | one-turn parent tools |
52
55
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
56
+ | `/write-plan` plan-only | recap + `next: /implement-plan` |
53
57
 
54
- You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + echo only. Siblings still run: `[grunt]:` echo. Illegal: `[grunt done]` `[[agent] done]` wait-prose.
58
+ You do not talk. First token = spawn. No try-then-spawn. No parent probe. No skip-spawn. No trivia/cheap. User-visible = legal tag + **one-line** echo only. Siblings still run: `[grunt]:` echo. Facts/search/trees → grunt, never thinker.
55
59
  Spawn only `grunt` | `implementer` | `thinker`. Omit `model`. Isolation `none` unless asked. Never spawn `orchestrator`. Children never spawn.
56
60
  grunt ← tools. World fact → grunt `job: web`. Never memory. implementer ← specified solution (not the spec). thinker ← plan/deep reason/spec.
57
61
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
@@ -6,14 +6,14 @@ claudecode:
6
6
  model: opus
7
7
  effort: high
8
8
  permissionMode: bypassPermissions
9
- tools: [Read, Grep, Glob]
10
- disallowedTools: [Agent, Write, Edit, Bash]
9
+ tools: [Read]
10
+ disallowedTools: [Agent, Write, Edit, Bash, Grep, Glob]
11
11
  grokcli:
12
12
  model: grok-4.6
13
13
  agents_md: false
14
14
  mcpInheritance: none
15
15
  permission_mode: bypassPermissions
16
- tools: read_file, grep, list_dir
16
+ tools: read_file
17
17
  codexcli:
18
18
  model: gpt-5.4
19
19
  model_reasoning_effort: high
@@ -25,14 +25,14 @@ antigravity-cli:
25
25
  mainAgent: false
26
26
  inheritMcp: false
27
27
  commandExecutionPolicy: eager
28
- tools: [view_file, grep_search]
28
+ tools: [view_file]
29
29
  geminicli:
30
30
  model: gemini-2.5-pro
31
31
  ---
32
32
  Voice: `.rulesync/reference/output.md` — must follow.
33
33
 
34
34
  Plan the solution. Deep reason. Never spawn. Never implement (implementer agent). Never run for simple tool usage (grunt agent).
35
- Read-only. No bash. Tiny Read/Grep/list_dir only if needed to plan. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
35
+ Read-only. No bash. Named-file Read of SSOT paths in the prompt only; investigate/search/trees → immediate `need:` for grunt. Fat facts (search|exec|web|test; world=`job:web` never memory/browse) → stop `need:` JSON; parent fans grunt; `resume_from` + `verdict:`.
36
36
  Return the spec/plan. Flag edge cases/pitfalls.
37
37
 
38
38
  Fat dump → stop on this JSON only (≤4/batch; no serial known-parallel):
package/AGENTS.md CHANGED
@@ -1,10 +1,13 @@
1
1
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
2
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + one-line echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
2
3
  All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
3
4
  Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
4
5
  Always-do:
5
6
 
6
7
  | signal | next |
7
8
  |---|---|
9
+ | create/change product files | spawn implementer (thinker then implementer if no spec). Never parent Write/Bash/Skill |
10
+ | plan/spec ready + file writes remain | persist plan if needed; spawn implementer; do not recap-stop |
8
11
  | tools/facts/git/test/mechanical write | spawn grunt |
9
12
  | world fact | spawn grunt `job:web` |
10
13
  | write defined solution | spawn implementer |
@@ -13,13 +16,14 @@ Always-do:
13
16
  | child `need:` | parse-need + parallel grunt; `resume_from` + new verdicts; max 3 |
14
17
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
15
18
  | work remains | spawn; do not stop |
16
- | children done + no findings | recap; stop allowed |
19
+ | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
17
20
  | `/parent` | one-turn parent tools |
18
21
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
22
+ | `/write-plan` plan-only | recap + `next: /implement-plan` |
19
23
 
20
24
  Parent = orchestrator. You do not talk. Spawn wait echo.
21
25
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
22
- First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + echo only. Siblings still run: `[grunt]:` echo. Illegal: `[grunt done]` `[[agent] done]` wait-prose. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
26
+ First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
23
27
  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.
24
28
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
25
29
  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,10 +1,13 @@
1
1
  Voice: `.rulesync/reference/output.md` — cite once; apply every turn.
2
+ en-US unless asked. every output maximal superterse. Fragments OK. Sacrifice grammar; keep meaning. Parent and child. Every turn, not final-only. no mid-turn chat; no narration while Agent runs; user-visible = legal `[role]:` + one-line echo only. Zero user-visible tokens before spawn/peek. In-flight host Stop → only `[orchestrator]: wait grunt`.
2
3
  All output exclude AI attribution, Co-Authored-By, trailers, generated markers.
3
4
  Protocol: `.rulesync/reference/cascade.md`. Do not paste. Do not open first.
4
5
  Always-do:
5
6
 
6
7
  | signal | next |
7
8
  |---|---|
9
+ | create/change product files | spawn implementer (thinker then implementer if no spec). Never parent Write/Bash/Skill |
10
+ | plan/spec ready + file writes remain | persist plan if needed; spawn implementer; do not recap-stop |
8
11
  | tools/facts/git/test/mechanical write | spawn grunt |
9
12
  | world fact | spawn grunt `job:web` |
10
13
  | write defined solution | spawn implementer |
@@ -13,13 +16,14 @@ Always-do:
13
16
  | child `need:` | parse-need + parallel grunt; `resume_from` + new verdicts; max 3 |
14
17
  | ⚠ / validate / sim | spawn implementer with findings; do not recap; spawn; no parent-edit |
15
18
  | work remains | spawn; do not stop |
16
- | children done + no findings | recap; stop allowed |
19
+ | children done + no findings | recap; stop allowed only if no writes remain and spawn count > 0 when the user asked for a file change |
17
20
  | `/parent` | one-turn parent tools |
18
21
  | `/handoff` | one-turn write `.tmp/grunt/handoffs/`; recap `[handoff]:` |
22
+ | `/write-plan` plan-only | recap + `next: /implement-plan` |
19
23
 
20
24
  Parent = this file not `.claude/agents/orchestrator.md`. You do not talk. Spawn wait echo.
21
25
  Spawn only grunt|implementer|thinker. Never spawn orchestrator. Children never spawn.
22
- First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + echo only. Siblings still run: `[grunt]:` echo. Illegal: `[grunt done]` `[[agent] done]` wait-prose. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
26
+ First token = spawn. No try-then-spawn. No parent probe. After child: legal tag + one-line echo only. Siblings still run: `[grunt]:` echo. No skip-spawn. /parent = one-turn. /handoff = one-turn in-parent write to `.tmp/grunt/handoffs/`; recap `[handoff]:`.
23
27
  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.
24
28
  Child prompt first sentence only: `You are {agent} subagent.` Then task + abs paths + verdicts only.
25
29
  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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovrozagar/grunt",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
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",
@@ -8,7 +8,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
8
8
  export const DEFAULT_GREP_HEAD_LIMIT = 50;
9
9
  /** Parent Read line limit when the model omits one. */
10
10
  export const DEFAULT_READ_LIMIT = 200;
11
- /** Implementer/thinker (and unknown non-grunt) Grep head_limit when omitted. */
11
+ /** Implementer (and unknown non-grunt) Grep head_limit when omitted. */
12
12
  export const CHILD_GREP_HEAD_LIMIT = 150;
13
13
  /** Implementer/thinker (and unknown non-grunt) Read line limit when omitted. */
14
14
  export const CHILD_READ_LIMIT = 400;
@@ -37,6 +37,8 @@ export const REASON_HEAD_LIMIT =
37
37
  export const REASON_FILE_SIZE = "spawn grunt job:search; file >200KB";
38
38
  export const REASON_IMPLEMENTER_BASH =
39
39
  "need: grunt job:exec|test query:…";
40
+ export const REASON_THINKER_TOOLS = "need: grunt job:search query:…";
41
+ export const REASON_IMPLEMENTER_WRITE = "need: path not in spec/plan";
40
42
 
41
43
  const READ_TOOLS = new Set(["read", "readfile"]);
42
44
  const GREP_TOOLS = new Set(["grep", "grepsearch"]);
@@ -77,19 +79,69 @@ export function eventKey(s) {
77
79
  .replace(/[^a-z0-9]/g, "");
78
80
  }
79
81
 
82
+ const CHILD_TYPE_KEYS = [
83
+ "subagentType",
84
+ "subagent_type",
85
+ "agentType",
86
+ "agent_type",
87
+ "agentName",
88
+ "agent_name",
89
+ ];
90
+ const CHILD_ID_KEYS = ["agentId", "agent_id", "spawnedBy", "spawned_by"];
91
+
92
+ function nonEmptyChildId(raw) {
93
+ if (raw == null) return false;
94
+ return String(raw).trim() !== "";
95
+ }
96
+
97
+ /** True when PreToolUse names a child session (UUID ok). Not a fat-gate type. */
98
+ export function hasChildAgentMarker(data) {
99
+ if (!data || typeof data !== "object") return false;
100
+ for (const k of CHILD_ID_KEYS) {
101
+ if (nonEmptyChildId(data[k])) return true;
102
+ }
103
+ if (data.agent && typeof data.agent === "object" && !Array.isArray(data.agent)) {
104
+ for (const k of [...CHILD_ID_KEYS, "id"]) {
105
+ if (nonEmptyChildId(data.agent[k])) return true;
106
+ }
107
+ }
108
+ return false;
109
+ }
110
+
111
+ function coerceChildType(raw) {
112
+ if (raw == null) return "";
113
+ const s = String(raw).trim().toLowerCase();
114
+ if (!s) return "";
115
+ if (
116
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s)
117
+ ) {
118
+ return "";
119
+ }
120
+ if (/^[0-9]+$/.test(s)) return "";
121
+ if (s.length > 64 || /[\/\\]/.test(s)) return "";
122
+ return s;
123
+ }
124
+
80
125
  export function subagentTypeOf(data) {
81
126
  if (!data || typeof data !== "object") return "";
82
- const raw =
83
- data.subagentType ??
84
- data.subagent_type ??
85
- data.agentType ??
86
- data.agent_type ??
87
- "";
88
- return String(raw).trim().toLowerCase();
127
+ for (const k of [...CHILD_TYPE_KEYS, ...CHILD_ID_KEYS]) {
128
+ const t = coerceChildType(data[k]);
129
+ if (t) return t;
130
+ }
131
+ if (data.agent && typeof data.agent === "object" && !Array.isArray(data.agent)) {
132
+ for (const k of ["subagentType", "subagent_type", "type", "name", "agentType"]) {
133
+ const t = coerceChildType(data.agent[k]);
134
+ if (t) return t;
135
+ }
136
+ } else if (typeof data.agent === "string") {
137
+ const t = coerceChildType(data.agent);
138
+ if (t) return t;
139
+ }
140
+ return "";
89
141
  }
90
142
 
91
143
  export function isParentOrchestrator(data) {
92
- return !subagentTypeOf(data);
144
+ return !subagentTypeOf(data) && !hasChildAgentMarker(data);
93
145
  }
94
146
 
95
147
  export function toolInputOf(data) {
@@ -175,8 +227,8 @@ export function rewriteGruntScratchPath(filePath, workspaceRoot) {
175
227
 
176
228
  export function workspaceRootOf(data) {
177
229
  return (
178
- process.env.GROK_WORKSPACE_ROOT ||
179
230
  (data && (data.workspaceRoot || data.workspace_root || data.cwd)) ||
231
+ process.env.GROK_WORKSPACE_ROOT ||
180
232
  process.cwd()
181
233
  );
182
234
  }
@@ -378,6 +430,110 @@ export function implementerBashReason(command, workspaceRoot) {
378
430
  return null;
379
431
  }
380
432
 
433
+ function resolveWriteAbs(filePath, workspaceRoot) {
434
+ if (!filePath || typeof filePath !== "string") return null;
435
+ return path.isAbsolute(filePath)
436
+ ? path.resolve(filePath)
437
+ : path.resolve(workspaceRoot, filePath);
438
+ }
439
+
440
+ function isUnderPlansDir(abs, workspaceRoot) {
441
+ if (!abs || !workspaceRoot) return false;
442
+ const dir = path.resolve(workspaceRoot, ".tmp", "plans");
443
+ if (abs === dir) return true;
444
+ const rel = path.relative(dir, abs);
445
+ return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
446
+ }
447
+
448
+ function isUnsolicitedDocPath(abs) {
449
+ if (!abs) return false;
450
+ if (path.basename(abs) === "README.md") return true;
451
+ const segs = abs.replace(/\\/g, "/").split("/");
452
+ return segs.includes("docs") || segs.includes("examples");
453
+ }
454
+
455
+ function addPlanPath(set, raw, workspaceRoot) {
456
+ let s = String(raw || "").trim();
457
+ if (!s) return;
458
+ if (/^https?:\/\//i.test(s)) return;
459
+ s = s.replace(/[.,;:]+$/, "");
460
+ if (!s || /^https?:\/\//i.test(s)) return;
461
+ if (/^\/[A-Za-z][\w-]*$/.test(s)) return;
462
+ if (!s.includes("/") && !s.includes("\\") && !s.includes(".")) return;
463
+ const abs = resolveWriteAbs(s, workspaceRoot);
464
+ if (abs) set.add(abs);
465
+ }
466
+
467
+ function extractPlanPaths(text, workspaceRoot) {
468
+ const set = new Set();
469
+ const body = String(text || "");
470
+ for (const m of body.matchAll(/`([^`\n]+)`/g)) {
471
+ addPlanPath(set, m[1], workspaceRoot);
472
+ }
473
+ for (const m of body.matchAll(
474
+ /(^|[\s("'[=])(\/(?:[A-Za-z0-9._@+-]+\/)+[A-Za-z0-9._@+-]+)/g,
475
+ )) {
476
+ addPlanPath(set, m[2], workspaceRoot);
477
+ }
478
+ return set;
479
+ }
480
+
481
+ function inProgressStatus(text) {
482
+ const s = String(text || "");
483
+ if (!s.startsWith("---\n")) return false;
484
+ const end = s.indexOf("\n---\n", 4);
485
+ if (end === -1) return false;
486
+ return /^status:\s*in-progress\s*$/m.test(s.slice(4, end));
487
+ }
488
+
489
+ function loadInProgressPlans(workspaceRoot) {
490
+ const dir = path.resolve(workspaceRoot, ".tmp", "plans");
491
+ let names;
492
+ try {
493
+ names = fs.readdirSync(dir);
494
+ } catch {
495
+ return [];
496
+ }
497
+ const out = [];
498
+ for (const name of names) {
499
+ if (!name.endsWith(".md")) continue;
500
+ const abs = path.resolve(dir, name);
501
+ let text;
502
+ try {
503
+ text = fs.readFileSync(abs, "utf8");
504
+ } catch {
505
+ continue;
506
+ }
507
+ if (!inProgressStatus(text)) continue;
508
+ out.push({ abs, text });
509
+ }
510
+ return out;
511
+ }
512
+
513
+ function implementerWriteDeny(input, workspaceRoot) {
514
+ const ws = path.resolve(workspaceRoot);
515
+ const plans = loadInProgressPlans(ws);
516
+ const allow = new Set();
517
+ for (const p of plans) {
518
+ allow.add(p.abs);
519
+ for (const extracted of extractPlanPaths(p.text, ws)) {
520
+ allow.add(extracted);
521
+ }
522
+ }
523
+ const hasInProgress = plans.length > 0;
524
+ for (const key of WRITE_PATH_FIELDS) {
525
+ if (typeof input[key] !== "string" || !input[key]) continue;
526
+ const abs = resolveWriteAbs(input[key], ws);
527
+ if (!abs) continue;
528
+ if (isUnderPlansDir(abs, ws)) continue;
529
+ if (allow.has(abs)) continue;
530
+ if (isUnsolicitedDocPath(abs) || hasInProgress) {
531
+ return { type: "deny", reason: REASON_IMPLEMENTER_WRITE };
532
+ }
533
+ }
534
+ return null;
535
+ }
536
+
381
537
  /**
382
538
  * @returns {null | { type: "deny", reason: string } | { type: "rewrite", updatedInput: object }}
383
539
  */
@@ -399,6 +555,10 @@ export function processFatTools(data) {
399
555
  changed = true;
400
556
  }
401
557
  }
558
+ if (subagentTypeOf(data) === "implementer") {
559
+ const denied = implementerWriteDeny(next, ws);
560
+ if (denied) return denied;
561
+ }
402
562
  return changed ? { type: "rewrite", updatedInput: next } : null;
403
563
  }
404
564
  const sub = subagentTypeOf(data);
@@ -410,6 +570,10 @@ export function processFatTools(data) {
410
570
  const isGlob = GLOB_TOOLS.has(toolKey);
411
571
  const isBash = BASH_TOOLS.has(toolKey);
412
572
 
573
+ if (sub === "thinker" && (isGrep || isGlob || isBash)) {
574
+ return { type: "deny", reason: REASON_THINKER_TOOLS };
575
+ }
576
+
413
577
  if (isRead || isGrep || isGlob) {
414
578
  for (const v of collectPathValues(input)) {
415
579
  if (pathIsDenied(v)) return { type: "deny", reason: REASON_DENYLIST };
@@ -123,6 +123,28 @@ export function capSpawnPrompt(prompt) {
123
123
  return (again.body + verdictPart).trim();
124
124
  }
125
125
 
126
+ const SPAWN_TYPE_KEYS = ["subagent_type", "subagentType", "name", "type", "agent"];
127
+ const FORBIDDEN_SPAWN_TYPES = new Set([
128
+ "explore",
129
+ "plan",
130
+ "general-purpose",
131
+ "orchestrator",
132
+ ]);
133
+
134
+ function spawnTypeCandidate(toolInput) {
135
+ for (const k of SPAWN_TYPE_KEYS) {
136
+ if (!Object.prototype.hasOwnProperty.call(toolInput, k)) continue;
137
+ if (toolInput[k] == null) continue;
138
+ const v = String(toolInput[k]).trim();
139
+ if (!v) continue;
140
+ const key = v.toLowerCase();
141
+ if (ALLOWED_TYPES.has(key)) return key;
142
+ if (FORBIDDEN_SPAWN_TYPES.has(key)) return "";
143
+ return "";
144
+ }
145
+ return "";
146
+ }
147
+
126
148
  export function rewriteSpawnToolInput(toolInput, { defaultGrunt = false } = {}) {
127
149
  if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
128
150
  return defaultGrunt ? { subagent_type: "grunt" } : null;
@@ -132,13 +154,13 @@ export function rewriteSpawnToolInput(toolInput, { defaultGrunt = false } = {})
132
154
  let changed = false;
133
155
 
134
156
  if (defaultGrunt) {
135
- const cur =
136
- toolInput.subagent_type != null
137
- ? String(toolInput.subagent_type).trim()
138
- : toolInput.subagentType != null
139
- ? String(toolInput.subagentType).trim()
140
- : "";
141
- if (!ALLOWED_TYPES.has(cur)) {
157
+ const cur = spawnTypeCandidate(toolInput);
158
+ if (ALLOWED_TYPES.has(cur)) {
159
+ if (String(next.subagent_type || "") !== cur) {
160
+ next.subagent_type = cur;
161
+ changed = true;
162
+ }
163
+ } else {
142
164
  next.subagent_type = "grunt";
143
165
  if (Object.prototype.hasOwnProperty.call(toolInput, "subagentType")) {
144
166
  next.subagentType = "grunt";