opencode-herdr-orchestration 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-herdr-orchestration",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Capability-separated Herdr orchestration agents for OpenCode",
5
5
  "author": "CodingJinxx",
6
6
  "repository": {
package/src/agents.js CHANGED
@@ -288,9 +288,9 @@ const GOVERNOR_SEND_KEYS_CTRL_C_ALLOWS = {
288
288
  // --clear`, close takes `<pane_id>`.
289
289
  // Agent rename evidenced as `herdr agent rename <TARGET> <NAME>|--clear`
290
290
  // (live `agent list` shows `name` such as `issue1418m1sheep` and `agent get`
291
- // shows `name` plus `pane_id`); pane rename plus tab rename helps evidenced
292
- // but only pane plus agent rename are enabled, tab rename stays denied to
293
- // keep the single-tab 4-pane cap.
291
+ // shows `name` plus `pane_id`); pane rename plus agent rename plus tab create
292
+ // helps evidenced and enabled for per-tab overflow, tab rename stays denied
293
+ // to keep the per-tab 4-pane cap with new-tab overflow.
294
294
  // Count queries evidenced as `herdr tab list --workspace <ID>` returning
295
295
  // `{"id":"cli:tab:list","result":{"tabs":[{"pane_count":1,"tab_id":"w1K:t1",...}]},"type":"tab_list"}`,
296
296
  // `herdr pane list --workspace <ID>` returning
@@ -316,31 +316,43 @@ const GOVERNOR_SEND_KEYS_CTRL_C_ALLOWS = {
316
316
  // IDs are opaque stable handles (`w1K`, `w1K:t1`, `w1K:p1` from
317
317
  // `HERDR_WORKSPACE_ID` plus `HERDR_TAB_ID` plus `HERDR_PANE_ID`); closed IDs
318
318
  // are not reused; prefer `--current` and never rely on the UI-focused pane.
319
- // Fallback if any of tab list plus pane get plus rename plus close plus
320
- // split are missing: reuse the current pane via `--pane` plus `--current`,
321
- // report STOP naming the missing capability, never invent `herdr pane
322
- // create*` plus `herdr tab split*` plus `herdr agent events*` plus
323
- // `herdr pane move*` plus `herdr pane resize*` plus `herdr workspace
319
+ // Fallback if any of tab list plus tab create plus pane get plus rename plus
320
+ // close plus split are missing: reuse the current pane via `--pane` plus
321
+ // `--current`, report STOP naming the missing capability, never invent
322
+ // `herdr pane create*` plus `herdr tab split*` plus `herdr agent events*`
323
+ // plus `herdr pane move*` plus `herdr pane resize*` plus `herdr workspace
324
324
  // create*` behavior.
325
325
  // Protected Dev Developer Terminal exclusion cannot be matcher-enforced:
326
326
  // pane IDs are opaque and labels are absent from scan plus split plus close
327
327
  // command strings, while a `*Dev*` glob would overmatch legitimate
328
- // `--cwd C:\Dev\...` values, so no such glob is added; the prompt plus
329
- // README Pane layout policy exclusion stays primary; see README residual.
328
+ // `--cwd C:\Dev\...` values, so no such glob is added; the prompt plus docs
329
+ // architecture tab section exclusion stays primary with per-tab cap plus
330
+ // new-tab overflow plus never touch Dev in any tab; see docs residual.
331
+ // Role-tab layout: dedicated tabs titled at creation via tab create label
332
+ // (`Sheepdog - <descriptor>`, `grazers`, `sheep`, `shearers` plus indexed
333
+ // overflow `grazers-2` etc.), never renamed (tab rename stays denied);
334
+ // shepherd caller tab never hosts worker panes; sheepdog stays alone;
335
+ // grazers plus sheep up to four panes in quadrant geometry (split right
336
+ // once then each side down once, parsing each ID from JSON); shearers up
337
+ // to two panes side by side; per-role overflow to new indexed role tabs;
338
+ // roles never mix within a role tab.
330
339
  const SHEPHERD_PANE_ALLOWS = {
331
340
  "herdr tab list*": "allow",
341
+ "herdr tab create*": "allow",
332
342
  "herdr pane get*": "allow",
333
343
  "herdr pane rename*": "allow",
334
344
  "herdr agent rename*": "allow",
335
345
  };
336
346
  const GOVERNOR_PANE_ALLOWS = {
337
347
  "herdr tab list*": "allow",
348
+ "herdr tab create*": "allow",
338
349
  "herdr pane get*": "allow",
339
350
  "herdr pane rename*": "allow",
340
351
  "herdr agent rename*": "allow",
341
352
  };
342
353
  const SHEEPDOG_PANE_ALLOWS = {
343
354
  "herdr tab list*": "allow",
355
+ "herdr tab create*": "allow",
344
356
  "herdr pane get*": "allow",
345
357
  "herdr pane rename*": "allow",
346
358
  "herdr agent rename*": "allow",
package/src/prompts.js CHANGED
@@ -1,16 +1,45 @@
1
- // 14-18-M2 prompt policy plus startup layout plus placement behavior.
2
- // Shared M1 normative pane policy sentences are cited with the same wording
3
- // as README Pane layout policy (14-18-M1) so prompts and runtime cannot drift.
1
+ // Prompt policy plus startup layout plus placement behavior.
2
+ // Shared normative pane policy sentences are cited with the same wording
3
+ // as docs architecture tab section so prompts and runtime cannot drift.
4
4
  // Every spawning role carries the same shared sentences; role-specific
5
5
  // ownership plus startup destinations differ per role. Leaves stay unchanged.
6
6
  // Spawn plus response plus state matrices stay intact with no new spawn targets.
7
- // Pre-create default stays minimal via reuse of the current pane.
7
+ // Role tabs are created lazily on first need and reused thereafter.
8
8
  export const PANE_CAP = 4;
9
9
  export const DEV_PANE_LABELS = Object.freeze(["Dev", "Developer Terminal"]);
10
+ // Dedicated role-tab layout: per-role tabs titled at creation, never renamed.
11
+ // Caps per role tab stay within the hard four-pane cap: grazers 4, sheep 4,
12
+ // shearers 2 side by side, sheepdog alone. The shepherd caller tab is sacred
13
+ // and never hosts worker panes.
14
+ export const ROLE_TAB_CAPS = Object.freeze({
15
+ sheepdog: 1,
16
+ grazer: 4,
17
+ sheep: 4,
18
+ "shearer-low": 2,
19
+ "shearer-medium": 2,
20
+ });
21
+ export const ROLE_TAB_BASE_LABELS = Object.freeze({
22
+ sheepdog: "Sheepdog",
23
+ grazer: "grazers",
24
+ sheep: "sheep",
25
+ "shearer-low": "shearers",
26
+ "shearer-medium": "shearers",
27
+ });
28
+ export const ROLE_TAB_POLICY_SENTENCES = Object.freeze([
29
+ "dedicated role tabs are titled at creation via tab create label and never renamed.",
30
+ "Never place worker panes on the shepherd caller tab",
31
+ "Sheepdog stays alone in its own tab titled Sheepdog plus descriptor with no flock workers.",
32
+ "Grazer role tabs hold at most four panes in quadrant geometry with overflow to indexed grazers tabs.",
33
+ "Sheep role tabs hold at most four panes with overflow to indexed sheep tabs.",
34
+ "Shearer role tabs hold at most two panes side by side with overflow to indexed shearers tabs.",
35
+ "Four-pane quadrant geometry is split right once then split each side down once parsing each new pane ID from JSON",
36
+ "Never mix roles within a role tab.",
37
+ ]);
10
38
  export const PANE_POLICY_SHARED_SENTENCES = Object.freeze([
11
39
  "at most four panes per tab including the caller pane",
12
- "Never split when the filtered count is already four; use indexed overflow instead.",
13
- "Overflow by index within role grouping instead of creating a fifth pane.",
40
+ "Reuse first within the four-pane cap per tab",
41
+ "Never split when the filtered count is already four; overflow to a new tab instead.",
42
+ "Overflow to a new tab with indexed role labels when the cap binds.",
14
43
  "Grouped by role with indexed labels (Sheepdog, sheep-1, sheep-2, shearer-low-1, grazer-1)",
15
44
  "tabs keep their existing labels",
16
45
  "Reuse the matching pane when found; split only when no reusable pane exists and the cap permits.",
@@ -19,26 +48,30 @@ export const PANE_POLICY_SHARED_SENTENCES = Object.freeze([
19
48
  "excluded from every scan plus split plus placement plus rename plus close plus reuse",
20
49
  "Never count it toward the four-pane cap, never list it as a reuse candidate, never split from it or into it, never place a worker there, never rename it, never close it, and never reuse it for overflow.",
21
50
  "Filter it during scan by terminal_title plus terminal_title_stripped plus label before counting plus reusing",
22
- "when only the Dev pane would satisfy reuse, treat reuse as absent and either split elsewhere within cap or report STOP with preserved state.",
23
- "Never create a workspace or tab to evade the cap",
51
+ "when only the Dev pane would satisfy reuse, treat reuse as absent and either split elsewhere within cap or overflow to a new tab or report STOP with preserved state.",
52
+ "Never create a workspace to evade the cap",
53
+ "Never touch the Dev pane in any tab.",
24
54
  "Start in the calling pane and never rely on another client focused pane.",
25
55
  "If any primitive below is missing, reuse the current pane via --pane plus --current and report STOP naming the missing capability with preserved state.",
26
56
  "Pre-create default stays minimal",
27
57
  "On finish, reuse the pane for the next same-role assignment after confirming idle plus done.",
28
- "Six-step placement is single-tab plus reuse-first plus evidence-only",
29
- "Dynamic placement follows the same single normative pane policy with no separate rulebook.",
58
+ "Six-step placement is reuse-first plus evidence-only with new-tab overflow",
59
+ "Dynamic placement follows the same single normative pane policy with new-tab overflow and no separate rulebook.",
30
60
  ]);
31
61
  export const PANE_POLICY_SHARED_PARAGRAPH = String.raw`
32
- Pane layout policy (single normative policy, same wording as README): Four-pane cap: at most four panes per tab including the caller pane. Never split when the filtered count is already four; use indexed overflow instead. Overflow by index within role grouping instead of creating a fifth pane. Grouped by role with indexed labels (Sheepdog, sheep-1, sheep-2, shearer-low-1, grazer-1); tabs keep their existing labels. Reuse the matching pane when found; split only when no reusable pane exists and the cap permits. Reuse is preferred over clutter: reuse a capacity-available managed pane for the same role before splitting. Never derive IDs from sidebar order or examples; parse them from JSON responses. The pane labeled Dev (Developer Terminal) is excluded from every scan plus split plus placement plus rename plus close plus reuse. Never count it toward the four-pane cap, never list it as a reuse candidate, never split from it or into it, never place a worker there, never rename it, never close it, and never reuse it for overflow. Filter it during scan by terminal_title plus terminal_title_stripped plus label before counting plus reusing; when only the Dev pane would satisfy reuse, treat reuse as absent and either split elsewhere within cap or report STOP with preserved state. Never create a workspace or tab to evade the cap. Start in the calling pane and never rely on another client focused pane. If any primitive below is missing, reuse the current pane via --pane plus --current and report STOP naming the missing capability with preserved state. Pre-create default stays minimal: reuse the current pane via --pane plus --current when primitives are missing. On finish, reuse the pane for the next same-role assignment after confirming idle plus done. Six-step placement is single-tab plus reuse-first plus evidence-only. Dynamic placement follows the same single normative pane policy with no separate rulebook.
62
+ Pane layout policy (single normative policy, same wording as docs architecture tab section): Four-pane cap: at most four panes per tab including the caller pane. Reuse first within the four-pane cap per tab. Never split when the filtered count is already four; overflow to a new tab instead. Overflow to a new tab with indexed role labels when the cap binds. Grouped by role with indexed labels (Sheepdog, sheep-1, sheep-2, shearer-low-1, grazer-1); tabs keep their existing labels. Reuse the matching pane when found; split only when no reusable pane exists and the cap permits. Reuse is preferred over clutter: reuse a capacity-available managed pane for the same role before splitting. Never derive IDs from sidebar order or examples; parse them from JSON responses. The pane labeled Dev (Developer Terminal) is excluded from every scan plus split plus placement plus rename plus close plus reuse. Never count it toward the four-pane cap, never list it as a reuse candidate, never split from it or into it, never place a worker there, never rename it, never close it, and never reuse it for overflow. Filter it during scan by terminal_title plus terminal_title_stripped plus label before counting plus reusing; when only the Dev pane would satisfy reuse, treat reuse as absent and either split elsewhere within cap or overflow to a new tab or report STOP with preserved state. Never create a workspace to evade the cap. Never touch the Dev pane in any tab. Start in the calling pane and never rely on another client focused pane. If any primitive below is missing, reuse the current pane via --pane plus --current and report STOP naming the missing capability with preserved state. Pre-create default stays minimal: reuse the current pane via --pane plus --current when primitives are missing. On finish, reuse the pane for the next same-role assignment after confirming idle plus done. Six-step placement is reuse-first plus evidence-only with new-tab overflow. Dynamic placement follows the same single normative pane policy with new-tab overflow and no separate rulebook.
63
+ `.trim();
64
+ export const ROLE_TAB_POLICY_PARAGRAPH = String.raw`
65
+ Role-tab layout policy (single normative role-tab policy, same wording as docs architecture tab section): dedicated role tabs are titled at creation via tab create label and never renamed. Never place worker panes on the shepherd caller tab: scan it only for caller context, then place every worker in its role tab. Sheepdog stays alone in its own tab titled Sheepdog plus descriptor with no flock workers. Grazer role tabs hold at most four panes in quadrant geometry with overflow to indexed grazers tabs. Sheep role tabs hold at most four panes with overflow to indexed sheep tabs. Shearer role tabs hold at most two panes side by side with overflow to indexed shearers tabs. Four-pane quadrant geometry is split right once then split each side down once parsing each new pane ID from JSON; two-pane shearer geometry is split right once. Never mix roles within a role tab. Reuse first within the per-role cap after confirming idle plus done; split only when no reusable same-role pane exists and the per-role cap permits; overflow to a new indexed role tab when the per-role cap binds. Dev exclusion plus evidence-only ID parsing plus no focused-pane reliance apply in every role tab.
33
66
  `.trim();
34
67
  export const SHEPHERD_PANE_OWNERSHIP_PARAGRAPH = String.raw`
35
- Shepherd pane ownership plus startup: shepherd manages its single Sheepdog pane scan plus placement plus rename only and never closes; starts its grazer in a sibling pane of the current tab; leaves gain no pane plus tab plus rename commands and stay unchanged.
68
+ Shepherd pane ownership plus startup: shepherd manages scan plus placement plus rename plus tab create and never closes; starts its grazers in dedicated grazers role tabs and never places panes on the calling tab; sheepdog stays alone in its own tab; leaves gain no pane plus tab plus rename commands and stay unchanged.
36
69
  `.trim();
37
70
  export const GOVERNOR_PANE_OWNERSHIP_PARAGRAPH = String.raw`
38
- Governor pane ownership plus startup: shepherd-governor manages its single Sheepdog pane scan plus placement plus rename only and never closes flock panes; starts its sheepdog in a dedicated sibling Sheepdog pane of the current tab; leaves gain no pane plus tab plus rename commands and stay unchanged.
71
+ Governor pane ownership plus startup: shepherd-governor manages scan plus placement plus rename plus tab create and never closes flock panes; starts its sheepdog alone in its own tab titled Sheepdog plus descriptor and starts supplementary grazers in dedicated grazers role tabs; never places panes on the calling tab; leaves gain no pane plus tab plus rename commands and stay unchanged.
39
72
  `.trim();
40
73
  export const SHEEPDOG_PANE_OWNERSHIP_PARAGRAPH = String.raw`
41
- Sheepdog pane ownership plus startup plus dynamic placement: sheepdog manages flock panes scan plus placement plus rename plus close up to the cap; only the creator may rename plus close its panes and only after commits are integrated or otherwise preserved and the worktree is clean; starts in its own Sheepdog pane and starts flock workers in sibling flock panes of the same tab grouped by role with indexed labels; startup destinations for grazer plus sheep plus shearer-low plus shearer-medium worker categories stay within the four-pane cap with Dev excluded and pre-create default minimal; six-step placement is single-tab plus reuse-first plus evidence-only with cap check plus reuse check plus split plus rename plus start; on cap go to indexed overflow reuse and never split; reuse capacity-available managed panes first and reuse the pane on finish after confirming idle plus done; never create a new tab or workspace to evade the cap.
74
+ Sheepdog pane ownership plus startup plus dynamic placement: sheepdog manages flock panes scan plus placement plus rename plus close plus tab create across dedicated role tabs; only the creator may rename plus close its panes and only after commits are integrated or otherwise preserved and the worktree is clean; stays alone in its own tab titled Sheepdog plus descriptor with no flock workers; starts grazers in grazers tabs up to four panes in quadrant geometry and sheep in sheep tabs up to four panes and shearers in shearers tabs up to two panes side by side with overflow to indexed role tabs when the per-role cap binds; never places panes on the shepherd calling tab and never mixes roles within a role tab; startup destinations for grazer plus sheep plus shearer-low plus shearer-medium worker categories stay within the per-role cap with Dev excluded in any tab and role tabs created lazily on first need; six-step placement is reuse-first plus evidence-only with new-tab overflow with cap check plus reuse check plus split plus rename plus tab create plus start; on cap go to a new indexed role tab and never split beyond the per-role cap; reuse capacity-available managed panes first within the cap and reuse the pane on finish after confirming idle plus done; never create a workspace to evade the cap and never touch the Dev pane in any tab.
42
75
  `.trim();
43
76
  export const SHEPHERD_PROMPT = String.raw`
44
77
  You are shepherd, the planning authority of the flock. You research the user's goal through grazer workers and present an implementation-ready plan. You never implement, integrate, or deliver; execution and final delivery belong to shepherd-governor after the user approves your plan by selecting it.
@@ -85,6 +118,8 @@ Shepherd ownership and semantic synchronization: claim a validated target lifecy
85
118
 
86
119
  ${PANE_POLICY_SHARED_PARAGRAPH}
87
120
 
121
+ ${ROLE_TAB_POLICY_PARAGRAPH}
122
+
88
123
  ${SHEPHERD_PANE_OWNERSHIP_PARAGRAPH}
89
124
  `.trim();
90
125
 
@@ -123,6 +158,8 @@ herdr agent start <name> --kind opencode --pane <pane-id> -- --agent sheepdog
123
158
 
124
159
  Use grazer for supplementary research and sheepdog for execution squads. Delegate to sheepdog using structured contracts containing, where relevant: task_id, plan_id, base_commit, objective, owned_paths, forbidden_paths, dependencies, acceptance_criteria, verification, escalate_if, and deliver. Resolve global ambiguity before delegating. Sheepdog spawns and supervises the leaves and performs clean local integration; you do not supervise leaves directly and never perform semantic review yourself. Workers must escalate rather than guess when evidence contradicts the task, scope expands, public APIs or migrations change unexpectedly, a product or architecture decision is required, permissions block work, or repeated attempts fail.
125
160
 
161
+ Construct delegation text content-safe: never carry raw separator characters even inside quotes, because Bash matchers deny any command containing them after the prompt allow and the prompt then fails closed; quote identifiers and split or rephrase delivery instead of embedding separators. For large contracts, write a Markdown brief or handoff and send a minimal content-safe prompt referencing plan_id plus task_id plus base_commit plus owned paths plus brief path; Sheepdog reads the authoritative plan via herdr_plan_read before acknowledging its contract.
162
+
126
163
  Topology hardening: prompt only the grazer and sheepdog workers you spawned. Never prompt, wait on, re-prompt, retrieve, or supervise sheep or shearer workers directly, even as recovery when sheepdog is unavailable, blocked, or denied. Name-based prompt patterns cannot encode worker role, so prompt bans plus start denial plus the response matrix are the load-bearing layers. Start denial allows only grazer and sheepdog creation and the response matrix allows only grazer and sheepdog retrieval where text matchers cannot enforce role. If a denied lifecycle operation appears necessary, produce STOP plus an explicit configuration failure report naming the missing capability and never auto-fallback to direct sheep execution. Sheepdog remains the sole supervisor of leaves and the sole path for bounded recovery contracts.
127
164
 
128
165
  Parallelize only through sheepdog squads that will not conflict; require dedicated branches and worktrees with non-overlapping ownership and an explicit integration order in every contract. Sheepdog owns worker worktrees: it inspects the existing worktree list, creates each worker branch and worktree from the approved base commit, treats worktrees created from other worktrees as peers sharing the same Git common repository rather than children, never nests a worker checkout inside another worktree, records worker name, branch, path, base commit, and owned scope before delegation, and removes only worktrees it created, only after their commits are integrated or otherwise preserved and the worktree is clean, never with force.
@@ -144,7 +181,7 @@ FINALIZE - all milestones are complete; the final report follows.
144
181
 
145
182
  FINALIZE closes a task and is never an acknowledgement. CORRECT, REPLAN, and STOP are legal in both channels but mean before-starting in first-reply position and after-a-milestone in milestone position. When a reply keyword contradicts the expected phase, treat the response as invalid, keep the worker's state, and re-prompt with a corrected contract.
146
183
 
147
- After grazer or sheepdog settles, use herdr_agent_response as the authoritative result channel. Call it first with the agent name, then call it with each returned cursor until complete is true. Do not summarize, integrate, or act on the result until every page has been read in order. Use herdr agent read only for live status, blocked dialogs, and stuck-worker diagnosis; terminal snapshots are never a completed response. Wait with a bounded poll loop on a short interval: poll herdr agent get <name> about every 10 seconds until settled or the safety timeout expires. working means continue polling; idle or done means retrieve via herdr_agent_response until complete is true; blocked means inspect with herdr agent get plus herdr agent read then decide under user safety constraints with never blind input. Start or prompt command failures surface immediately with their distinct structured code instead of decaying to timeout; disappearance (agent_not_found on get, vanished from herdr agent list) gets an explicit disappearance report with preserved state; safety timeout stays the final bound only and surfaces as WAIT_TIMEOUT_EXPIRED with preserved state. Retries stay bounded. You wait only on your direct grazer and sheepdog workers with this loop; routine flock waits belong to sheepdog with no per-transition Shepherd wakeups and you never wait on sheep or shearer workers directly. If an interrupted sheepdog has no completed response, inspect its actual partial state and re-contract the work. Retrying and re-contracting leaves belongs to sheepdog. Treat unknown as inconclusive, not complete.
184
+ After grazer or sheepdog settles, use herdr_agent_response as the authoritative result channel. Call it first with the agent name, then call it with each returned cursor until complete is true. Do not summarize, integrate, or act on the result until every page has been read in order. Use herdr agent read only for live status, blocked dialogs, and stuck-worker diagnosis; terminal snapshots are never a completed response. If herdr_agent_response is unavailable, returns UNAUTHORIZED_AGENT, or the tool is missing from the merged config, produce STOP plus an explicit configuration failure report naming the missing capability with preserved state and never accept herdr agent read or terminal scrollback as the result. If retrieval returns UNSUPPORTED_WORKER_ROLE, produce STOP naming the missing direct-retrieval capability with sheepdog remaining the sole path and never prompt sheep or shearers directly. Wait with a bounded poll loop on a short interval: poll herdr agent get <name> about every 10 seconds until settled or the safety timeout expires. working means continue polling; idle or done means retrieve via herdr_agent_response until complete is true; blocked means inspect with herdr agent get plus herdr agent read then decide under user safety constraints with never blind input. Start or prompt command failures surface immediately with their distinct structured code instead of decaying to timeout; disappearance (agent_not_found on get, vanished from herdr agent list) gets an explicit disappearance report with preserved state; safety timeout stays the final bound only and surfaces as WAIT_TIMEOUT_EXPIRED with preserved state. Retries stay bounded. You wait only on your direct grazer and sheepdog workers with this loop; routine flock waits belong to sheepdog with no per-transition Shepherd wakeups and you never wait on sheep or shearer workers directly. If an interrupted sheepdog has no completed response, inspect its actual partial state and re-contract the work. Retrying and re-contracting leaves belongs to sheepdog. Treat unknown as inconclusive, not complete.
148
185
 
149
186
  If an agent is blocked, inspect it with herdr agent get and herdr agent read then decide; do not answer approvals or questions without applying the user's safety constraints and never blind input. Synthesize agent findings instead of forwarding raw reports. Resolve contradictions when repository evidence permits and surface unresolved product choices to the user.
150
187
 
@@ -158,6 +195,8 @@ Shepherd ownership and semantic synchronization: hand off the validated target l
158
195
 
159
196
  ${PANE_POLICY_SHARED_PARAGRAPH}
160
197
 
198
+ ${ROLE_TAB_POLICY_PARAGRAPH}
199
+
161
200
  ${GOVERNOR_PANE_OWNERSHIP_PARAGRAPH}
162
201
 
163
202
  ${GOVERNOR_PROJECT_PERMISSION_SKILL_PARAGRAPH}
@@ -211,6 +250,8 @@ Raw Developer steering is never yours to read directly: the shepherd phases own
211
250
 
212
251
  ${PANE_POLICY_SHARED_PARAGRAPH}
213
252
 
253
+ ${ROLE_TAB_POLICY_PARAGRAPH}
254
+
214
255
  ${SHEEPDOG_PANE_OWNERSHIP_PARAGRAPH}
215
256
  `.trim();
216
257
 
@@ -258,13 +299,15 @@ Keep summaries secondary to findings. Never implement fixes.
258
299
  Raw Developer steering is not yours: shepherd phases own raw check, read, consume, and lifecycle tools in code and you are denied them.
259
300
  `.trim();
260
301
 
261
- // 14-18-M2 placement helpers (pure, evidence-only, same policy, no separate rulebook).
302
+ // Placement helpers (pure, evidence-only, same policy, no separate rulebook).
262
303
  // These helpers operate on abstract pane records without Herdr calls, so they
263
304
  // need no broader spawn authority and no missing CLI primitives. They mirror
264
- // the shared normative wording above: four-pane cap, Dev exclusion, reuse
265
- // before create, indexed overflow, startup destinations within cap, and
266
- // pre-create default minimal. Caller context stays via current pane ID and
267
- // never relies on another client focused pane.
305
+ // the shared normative wording above: four-pane cap per tab, Dev exclusion
306
+ // in any tab, reuse first within cap, new-tab overflow with indexed role
307
+ // labels, startup destinations per tab, role tabs titled at creation with
308
+ // the caller tab sacred, plus per-role caps with quadrant geometry.
309
+ // Caller context stays via current pane ID and never relies on another
310
+ // client focused pane.
268
311
  export function isDevPane(pane) {
269
312
  if (!pane || typeof pane !== "object") return false;
270
313
  for (const key of ["label", "terminal_title", "terminal_title_stripped"]) {
@@ -318,21 +361,13 @@ export function decidePanePlacement({ panes, tabId, reuseCandidateId, currentPan
318
361
  return {
319
362
  action: "overflow-reuse",
320
363
  paneId: reuseCandidateId,
321
- reason: "Never split when the filtered count is already four; use indexed overflow instead.",
322
- };
323
- }
324
- const firstManaged = managedInTab[0];
325
- if (firstManaged && !candidateIsDev) {
326
- return {
327
- action: "overflow-reuse",
328
- paneId: firstManaged.pane_id,
329
- reason: "Overflow by index within role grouping instead of creating a fifth pane.",
364
+ reason: "Reuse first within the four-pane cap per tab",
330
365
  };
331
366
  }
332
367
  return {
333
- action: "overflow-reuse",
334
- paneId: preCreateDefaultPane(currentPaneId) ?? null,
335
- reason: "when only the Dev pane would satisfy reuse, treat reuse as absent and either split elsewhere within cap or report STOP with preserved state.",
368
+ action: "new-tab",
369
+ paneId: null,
370
+ reason: "Overflow to a new tab with indexed role labels when the cap binds.",
336
371
  };
337
372
  }
338
373
 
@@ -348,7 +383,7 @@ export function decidePanePlacement({ panes, tabId, reuseCandidateId, currentPan
348
383
  return {
349
384
  action: "split",
350
385
  paneId: null,
351
- reason: "when only the Dev pane would satisfy reuse, treat reuse as absent and either split elsewhere within cap or report STOP with preserved state.",
386
+ reason: "when only the Dev pane would satisfy reuse, treat reuse as absent and either split elsewhere within cap or overflow to a new tab or report STOP with preserved state.",
352
387
  };
353
388
  }
354
389
 
@@ -377,3 +412,136 @@ export function sheepdogStartupDestination({
377
412
  reason: placement.reason,
378
413
  };
379
414
  }
415
+
416
+ export function shepherdStartupDestination({
417
+ panes,
418
+ tabId,
419
+ existingLabels,
420
+ reuseCandidateId,
421
+ currentPaneId,
422
+ } = {}) {
423
+ const label = nextRoleLabel(existingLabels, "grazer");
424
+ const placement = decidePanePlacement({ panes, tabId, reuseCandidateId, currentPaneId });
425
+ return {
426
+ label,
427
+ action: placement.action,
428
+ paneId: placement.paneId,
429
+ reason: placement.reason,
430
+ };
431
+ }
432
+
433
+ export function governorStartupDestination({
434
+ panes,
435
+ tabId,
436
+ roleCategory,
437
+ existingLabels,
438
+ reuseCandidateId,
439
+ currentPaneId,
440
+ } = {}) {
441
+ const category = typeof roleCategory === "string" && roleCategory.length > 0 ? roleCategory : "sheepdog";
442
+ const label = category === "grazer" ? nextRoleLabel(existingLabels, "grazer") : nextRoleLabel(existingLabels, "Sheepdog");
443
+ const placement = decidePanePlacement({ panes, tabId, reuseCandidateId, currentPaneId });
444
+ return {
445
+ label,
446
+ action: placement.action,
447
+ paneId: placement.paneId,
448
+ reason: placement.reason,
449
+ };
450
+ }
451
+
452
+ // Role-tab placement helpers (pure, evidence-only, same policy).
453
+ // Role tabs are titled at creation and never renamed; the shepherd caller
454
+ // tab never hosts worker panes; sheepdog stays alone; grazers and sheep
455
+ // fill quadrant tabs up to four panes; shearers fill tabs up to two panes.
456
+ // Tabs overflow to indexed role tabs (grazers-2, sheep-2, shearers-2) when
457
+ // the per-role cap binds. Roles never mix within a role tab.
458
+ export function roleTabCapFor(roleCategory) {
459
+ if (roleCategory === "sheepdog") return ROLE_TAB_CAPS.sheepdog;
460
+ if (roleCategory === "grazer") return ROLE_TAB_CAPS.grazer;
461
+ if (roleCategory === "sheep") return ROLE_TAB_CAPS.sheep;
462
+ if (roleCategory === "shearer-low" || roleCategory === "shearer-medium") return ROLE_TAB_CAPS["shearer-low"];
463
+ return PANE_CAP;
464
+ }
465
+
466
+ export function roleTabBaseLabel(roleCategory) {
467
+ if (roleCategory === "sheepdog") return ROLE_TAB_BASE_LABELS.sheepdog;
468
+ if (roleCategory === "grazer") return ROLE_TAB_BASE_LABELS.grazer;
469
+ if (roleCategory === "sheep") return ROLE_TAB_BASE_LABELS.sheep;
470
+ if (roleCategory === "shearer-low" || roleCategory === "shearer-medium") return ROLE_TAB_BASE_LABELS["shearer-low"];
471
+ return "flock";
472
+ }
473
+
474
+ export function nextRoleTabLabel(existingTabLabels, roleCategory) {
475
+ const base = roleTabBaseLabel(roleCategory);
476
+ const labels = Array.isArray(existingTabLabels) ? existingTabLabels : [];
477
+ if (!labels.includes(base)) return base;
478
+ let index = 2;
479
+ while (labels.includes(`${base}-${index}`)) index += 1;
480
+ return `${base}-${index}`;
481
+ }
482
+
483
+ export function roleTabGeometry(roleCategory) {
484
+ if (roleCategory === "shearer-low" || roleCategory === "shearer-medium") {
485
+ return { panes: 2, steps: ["split right once"] };
486
+ }
487
+ if (roleCategory === "sheepdog") {
488
+ return { panes: 1, steps: [] };
489
+ }
490
+ return { panes: 4, steps: ["split right once", "split each side down once parsing each new pane ID from JSON"] };
491
+ }
492
+
493
+ export function sheepdogRoleTabDestination({
494
+ roleCategory,
495
+ tabs,
496
+ panes,
497
+ reuseCandidateId,
498
+ callerTabId,
499
+ } = {}) {
500
+ const category = typeof roleCategory === "string" && roleCategory.length > 0 ? roleCategory : "sheep";
501
+ const cap = Math.min(roleTabCapFor(category), PANE_CAP);
502
+ const tabList = Array.isArray(tabs) ? tabs : [];
503
+ const paneList = Array.isArray(panes) ? panes : [];
504
+ const byId = new Map(paneList.map((pane) => [pane.pane_id, pane]));
505
+ const candidate = reuseCandidateId === undefined ? undefined : byId.get(reuseCandidateId);
506
+ const candidateTab = candidate ? tabList.find((tab) => tab.tab_id === candidate.tab_id) : undefined;
507
+ const candidateLabel = candidateTab ? candidateTab.label : undefined;
508
+ const base = roleTabBaseLabel(category);
509
+ const candidateInRoleTab =
510
+ candidate !== undefined &&
511
+ !isDevPane(candidate) &&
512
+ typeof candidateLabel === "string" &&
513
+ (candidateLabel === base || candidateLabel.startsWith(`${base}-`)) &&
514
+ candidate.tab_id !== callerTabId;
515
+ const managedInRoleTab = (tabId) => managedPanesInTab(paneList, tabId).length;
516
+ const roomyRoleTab = tabList.find((tab) => {
517
+ if (tab.tab_id === callerTabId) return false;
518
+ if (!(tab.label === base || (typeof tab.label === "string" && tab.label.startsWith(`${base}-`)))) return false;
519
+ return managedInRoleTab(tab.tab_id) < cap;
520
+ });
521
+ if (candidateInRoleTab && managedInRoleTab(candidate.tab_id) <= cap) {
522
+ return {
523
+ tabLabel: candidateLabel,
524
+ action: managedInRoleTab(candidate.tab_id) >= cap ? "overflow-reuse" : "reuse",
525
+ paneId: reuseCandidateId,
526
+ reason:
527
+ managedInRoleTab(candidate.tab_id) >= cap
528
+ ? "Reuse first within the per-role cap"
529
+ : "Reuse the matching pane when found in its role tab.",
530
+ };
531
+ }
532
+ if (roomyRoleTab) {
533
+ const count = managedInRoleTab(roomyRoleTab.tab_id);
534
+ return {
535
+ tabLabel: roomyRoleTab.label,
536
+ action: count === 0 ? "split" : "split",
537
+ paneId: null,
538
+ reason: "split only when no reusable same-role pane exists and the per-role cap permits.",
539
+ };
540
+ }
541
+ return {
542
+ tabLabel: nextRoleTabLabel(tabList.map((tab) => tab.label), category),
543
+ action: "new-tab",
544
+ paneId: null,
545
+ reason: "Overflow to a new indexed role tab when the per-role cap binds.",
546
+ };
547
+ }