From 4750bd75cca4400b4e3ffbaa8334397e50f09679 Mon Sep 17 00:00:00 2001 From: TiewKH Date: Fri, 9 Oct 2026 11:34:14 +0800 Subject: [PATCH] Move the Herdr scripts into poteto-mode/scripts/herdr (0.9.77) Follow upstream's orch/ and watch-pr/ layout: herdr-dispatch.ts, herdr-config.ts, and configure-herdr.ts become herdr/dispatch.ts, herdr/config.ts, and herdr/configure.ts, with their tests beside them. Every skill, hook, doc, test, and CI path names the new location. Co-Authored-By: Claude Opus 5.5 --- .claude-plugin/marketplace.json | 2 +- CHANGES.md | 11 +++++++++++ CONTEXT.md | 4 ++-- HERDR.md | 10 +++++----- NOTICE.md | 2 +- README.md | 2 +- VERSION | 2 +- docs/reference.md | 2 +- package.json | 2 +- plugins/pstack/.claude-plugin/plugin.json | 2 +- plugins/pstack/.codex-plugin/plugin.json | 2 +- plugins/pstack/.github/plugin/plugin.json | 2 +- plugins/pstack/hooks/herdr-agent-gate.sh | 8 ++++---- .../skills/poteto-mode/references/herdr-tools.md | 10 +++++----- .../{herdr-config.test.ts => herdr/config.test.ts} | 6 +++--- .../scripts/{herdr-config.ts => herdr/config.ts} | 0 .../{configure-herdr.ts => herdr/configure.ts} | 4 ++-- .../dispatch.test.ts} | 4 ++-- .../scripts/{herdr-dispatch.ts => herdr/dispatch.ts} | 4 ++-- plugins/pstack/skills/setup-pstack/SKILL.md | 6 +++--- tests/herdr-agent-gate.test.mjs | 10 +++++----- tests/herdr-e2e.sh | 4 ++-- tests/herdr-runtime.test.mjs | 8 ++++---- 23 files changed, 59 insertions(+), 48 deletions(-) rename plugins/pstack/skills/poteto-mode/scripts/{herdr-config.test.ts => herdr/config.test.ts} (98%) rename plugins/pstack/skills/poteto-mode/scripts/{herdr-config.ts => herdr/config.ts} (100%) rename plugins/pstack/skills/poteto-mode/scripts/{configure-herdr.ts => herdr/configure.ts} (95%) rename plugins/pstack/skills/poteto-mode/scripts/{herdr-dispatch.test.ts => herdr/dispatch.test.ts} (99%) rename plugins/pstack/skills/poteto-mode/scripts/{herdr-dispatch.ts => herdr/dispatch.ts} (99%) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 23e89773..116143d7 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "name": "pstack", "source": "./plugins/pstack", "description": "If you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence. Original pstack by Lauren Tan (poteto).", - "version": "0.9.76" + "version": "0.9.77" } ] } diff --git a/CHANGES.md b/CHANGES.md index 2598be73..c876383a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,17 @@ This file is the release changelog, with one `## - ` entry per release, newest first. The Cursor-to-Claude rewrite rules live in [`tools/substitutions.json`](tools/substitutions.json), and the [sync boundary](CONTRIBUTING.md#the-sync-boundary) in `CONTRIBUTING.md` defines how a change to upstream's skill content is declared. +## 0.9.77 - pstack-herdr: move the Herdr scripts into their own folder + +The Herdr scripts move from the flat `poteto-mode/scripts/` directory into `poteto-mode/scripts/herdr/`, following upstream's layout for `orch/` and `watch-pr/`, where a tool's folder carries its name and the files inside drop it: + +- `herdr-dispatch.ts` is now `herdr/dispatch.ts`. +- `herdr-config.ts` is now `herdr/config.ts`. +- `configure-herdr.ts` is now `herdr/configure.ts`. +- Each test moves beside its script. + +The scripts keep the shared `bootstrap.ts`, `package.json`, and `tsconfig.json`, and their behavior does not change. `herdr-tools.md`, `setup-pstack`, the Agent gate, `HERDR.md`, the tests, and CI's Herdr dispatch job name the new paths. A session that started before the update and still runs an old path gets a missing-file error from `bun` until it reads `herdr-tools.md` again. + ## 0.9.76 - pstack-herdr: merge pstack-claude 0.9.75 This release merges `michael-denyer/pstack-claude` 0.9.70 through 0.9.75 into the fork. It brings in the GitHub Copilot runtime (plugin manifest, hooks, `copilot-tools.md`, and the Copilot setup path), the `/poteto-help` skill and the `cursor/plugins` pin at `2cbf585` (v0.15.13), Feature delegates whose worktrees start from the branch's `HEAD`, per-task fallback todolists, bounded Autopilot verify rounds, and fail-closed fixes in `watch-pr`, `orch`, `worktree-audit`, `sync`, and the release gate. The fork keeps the Herdr dispatcher, `--next`, the Agent gate, routes, and the real Herdr CI job. Upstream touched none of them. diff --git a/CONTEXT.md b/CONTEXT.md index 0254ad9f..26edc4e8 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -8,9 +8,9 @@ Names for the concepts this repo's design discussions keep reaching for. Archite - **Role** — a named unit of delegated work with its own model choice (`arena runners`, `how critics`, `swarm workers`). The role vocabulary lives in `models.json` and surfaces in `setup-pstack`'s sheet. - **Herdr route** — the Herdr-side model policy in `~/.config/pstack-herdr/routes.yaml`, written by `setup-pstack` inside Herdr. It maps each Herdr role (`explorer`, `reviewer`, ...) to a pool of profiles. Separate from the override sheet: Herdr workers never read `pstack-models.md`. - **Profile** — one worker configuration in a Herdr route: CLI kind, full model ID, optional effort, optional config home. Several workers can share one profile and its account limits. -- **Dispatcher** — `herdr-dispatch.ts`. Starts a worker in a Herdr pane, delivers the prompt, waits, reads the output, and closes the pane. Everything `herdr-tools.md` maps a native `Agent` call to. +- **Dispatcher** — `herdr/dispatch.ts`. Starts a worker in a Herdr pane, delivers the prompt, waits, reads the output, and closes the pane. Everything `herdr-tools.md` maps a native `Agent` call to. - **Handoff** — a `--wait` dispatch that returns `working`, `blocked`, or `unknown` and leaves the pane open. `--collect` finishes a `working` handoff and closes the pane. -- **Drain** — collecting background workers with `herdr-dispatch.ts --next <names...>`, which blocks until the first settles and lists the rest as pending. The orchestrator calls it again with the pending names instead of sleeping between checks. +- **Drain** — collecting background workers with `herdr/dispatch.ts --next <names...>`, which blocks until the first settles and lists the rest as pending. The orchestrator calls it again with the pending names instead of sleeping between checks. - **Panel** — the three-model diverse panel the multi-model skills (`arena`, `architect`, `interrogate`, `how`) run by default. One fact in `models.json`; the generator stamps every copy and fails on strays. - **Prompt stub** — an optional Codex slash command that invokes a skill. The generator writes it under `.codex-plugin/prompts/` using the skill's row in the [slash-command reference](docs/reference.md#slash-commands). Native skill discovery works without it. - **Menu description** — the short description in a public skill's row in the [slash-command reference](docs/reference.md#slash-commands). Codex displays it in the slash menu. The skill's frontmatter `description` tells the agent when to invoke it. diff --git a/HERDR.md b/HERDR.md index d05cc981..3d29d19a 100644 --- a/HERDR.md +++ b/HERDR.md @@ -1,6 +1,6 @@ # Herdr runtime -When `HERDR_ENV=1`, pstack delegates through [`herdr-dispatch.ts`](plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.ts) instead of the native `Agent` tool. pstack still decides decomposition, roles, isolation, synthesis, review, and verification. The dispatcher owns the worker's pane from creation to cleanup. [`herdr-tools.md`](plugins/pstack/skills/poteto-mode/references/herdr-tools.md) is the per-skill mapping agents read. This file is the contract behind it. +When `HERDR_ENV=1`, pstack delegates through [`herdr/dispatch.ts`](plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.ts) instead of the native `Agent` tool. pstack still decides decomposition, roles, isolation, synthesis, review, and verification. The dispatcher owns the worker's pane from creation to cleanup. [`herdr-tools.md`](plugins/pstack/skills/poteto-mode/references/herdr-tools.md) is the per-skill mapping agents read. This file is the contract behind it. ## Roles @@ -18,7 +18,7 @@ When `HERDR_ENV=1`, pstack delegates through [`herdr-dispatch.ts`](plugins/pstac ## Routing -`/setup-pstack` writes `~/.config/pstack-herdr/routes.yaml` through `configure-herdr.ts`. Do not edit it by hand. [`config/routes.example.yaml`](config/routes.example.yaml) shows the format. +`/setup-pstack` writes `~/.config/pstack-herdr/routes.yaml` through `herdr/configure.ts`. Do not edit it by hand. [`config/routes.example.yaml`](config/routes.example.yaml) shows the format. - A **profile** names a CLI (`kind: claude` or `codex`), a `model`, an optional `effort`, and optional `env`. - A **role** names a pool of profiles and a `strategy`. `first` always takes the first profile. `spread` hashes the worker name over the pool. `--kind` keeps only the pool's profiles of that CLI before the strategy runs. @@ -35,7 +35,7 @@ The caller's `--cwd` is the isolation boundary. Read-only workers may share a ch ## Dispatch lifecycle ```sh -bun <poteto-mode>/scripts/herdr-dispatch.ts \ +bun <poteto-mode>/scripts/herdr/dispatch.ts \ --role implementation \ --name feature-worker \ --prompt-file /tmp/feature-brief.md \ @@ -50,12 +50,12 @@ bun <poteto-mode>/scripts/herdr-dispatch.ts \ 5. The dispatcher appends a marker line, `[pstack-herdr turn <name>-<time>]`, to every Codex prompt. A Codex worker's turn ends when the rollout that quotes the marker, under the worker's `$CODEX_HOME/sessions`, records `task_complete` after it, whatever Herdr reports. The prompt also counts as delivered once that rollout quotes the marker. Herdr 0.9.2+ reads a finished Codex turn as `unknown`, and 0.9.1 can read a running one as `idle`. The dispatcher polls the rollout, returns `done` with the turn's final message as the output, and reports `working` while the rollout shows the turn still open. The dispatch saves the pane, sessions directory, marker, and start time to `$TMPDIR/pstack-herdr/<name>.json` before it types the prompt, so `--collect` checks the same turn. 6. A verified worker's output is read and its pane closed. `--keep-pane` keeps it open. -`working`, `blocked`, and `unknown` panes stay open. `blocked` means the worker is waiting on an approval or question, so inspect it. `unknown` is not completion. Finish a `working` worker with `herdr-dispatch.ts --collect --name <name>`, which waits, reads the output, and closes the pane once it is done. A dispatch or `--collect` stopped by SIGTERM, SIGINT, or SIGHUP while waiting closes the worker's pane first. SIGKILL cannot be caught and leaves the pane open. +`working`, `blocked`, and `unknown` panes stay open. `blocked` means the worker is waiting on an approval or question, so inspect it. `unknown` is not completion. Finish a `working` worker with `herdr/dispatch.ts --collect --name <name>`, which waits, reads the output, and closes the pane once it is done. A dispatch or `--collect` stopped by SIGTERM, SIGINT, or SIGHUP while waiting closes the worker's pane first. SIGKILL cannot be caught and leaves the pane open. For parallel fan-out, start every dispatcher process without `--wait`, then collect them with `--next`: ```sh -bun <poteto-mode>/scripts/herdr-dispatch.ts --next worker-a worker-b worker-c --timeout 600000 +bun <poteto-mode>/scripts/herdr/dispatch.ts --next worker-a worker-b worker-c --timeout 600000 ``` `--next` blocks until the first named worker settles, reads and closes every worker that has, and prints `{"settled": [...], "pending": [...]}`. Call it again with the `pending` names until none are left. Each Claude worker's status change reaches it through a Herdr `events.subscribe` stream on `HERDR_SOCKET_PATH`, so it wakes when the status changes. Reading the output and closing the pane takes under a second more. A Codex worker's rollout is read every second. Without the socket, it reads every second. `--next` only watches: a stopped `--next`, or one whose budget ends, leaves every worker open. It works the same from a Claude Code or a Codex orchestrator, so neither needs a sleep loop. diff --git a/NOTICE.md b/NOTICE.md index aef03111..9a3ea807 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -62,7 +62,7 @@ Summary of structural changes inherited by this fork: This fork adds Herdr as an execution transport. Playbooks keep their native spawn language; `references/herdr-tools.md` is the Herdr translation the same way `codex-tools.md` is the Codex translation. Herdr-specific authored files include: -- `plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.ts` +- `plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.ts` - `plugins/pstack/skills/poteto-mode/references/herdr-tools.md` - `config/routes.example.yaml` - `tests/herdr-runtime.test.mjs` diff --git a/README.md b/README.md index 7cf0f01b..da7a383f 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Use poteto-mode to fix the search filter resetting when I change pages. | Layer | Owns | | --- | --- | | pstack skills | decomposition, roles, worktree isolation, synthesis, review, verification | -| [`herdr-dispatch.ts`](plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.ts) | pane creation, profile environment, CLI startup, prompt delivery, waiting, output reads, pane cleanup, delegation depth | +| [`herdr/dispatch.ts`](plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.ts) | pane creation, profile environment, CLI startup, prompt delivery, waiting, output reads, pane cleanup, delegation depth | | [`herdr-agent-gate.sh`](plugins/pstack/hooks/herdr-agent-gate.sh) | on Claude Code, denies the native `Agent` tool inside Herdr and returns the dispatcher command | | Herdr | panes, agent status, visibility | | Claude Code, Codex | the delegated work | diff --git a/VERSION b/VERSION index 8667f8a0..1b525aba 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.76 +0.9.77 diff --git a/docs/reference.md b/docs/reference.md index 6b96cfa5..70448b40 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -174,7 +174,7 @@ Install dependencies for the workflows you use: | Dependency | When you need it | | --- | --- | | GitHub CLI, `gh` | PR monitoring and shipping. Authenticate with `gh auth login`. | -| Bun | The bundled `watch-pr`, `orch`, and `herdr-dispatch.ts` scripts. Their bootstrap installs script dependencies on first run. | +| Bun | The bundled `watch-pr`, `orch`, and `herdr` scripts. Their bootstrap installs script dependencies on first run. | | Herdr | Visible delegated workers. See [HERDR.md](../HERDR.md). | | Graphite CLI, `gt` | The Orchestrate playbook and `orch` stack frontier. Shipping and autopilot playbooks use `gh` or Origin's CLI when available. | | `plugin-dev` | Claude Code skill-authoring guidance used by `automate-me`, `reflect`, and `poteto-mode`. | diff --git a/package.json b/package.json index d9498bc0..d5d589e1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pstack", "private": true, - "version": "0.9.76", + "version": "0.9.77", "description": "If you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence. Pi package of the Claude Code plugin; skills are shared, tool names resolve via skills/poteto-mode/references/pi-tools.md. Original pstack by Lauren Tan (poteto).", "author": { "name": "Michael Denyer", diff --git a/plugins/pstack/.claude-plugin/plugin.json b/plugins/pstack/.claude-plugin/plugin.json index 387fa4a1..04641e31 100644 --- a/plugins/pstack/.claude-plugin/plugin.json +++ b/plugins/pstack/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "pstack", "displayName": "pstack (Claude Code port)", - "version": "0.9.76", + "version": "0.9.77", "description": "If you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence. Ported from cursor/plugins/pstack to Claude Code. Original pstack by Lauren Tan (poteto).", "author": { "name": "Michael Denyer", diff --git a/plugins/pstack/.codex-plugin/plugin.json b/plugins/pstack/.codex-plugin/plugin.json index 972d3dea..b1b9b07b 100644 --- a/plugins/pstack/.codex-plugin/plugin.json +++ b/plugins/pstack/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pstack", - "version": "0.9.76", + "version": "0.9.77", "description": "If you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence. Codex port of the Claude Code plugin; skills are shared, tool names resolve via skills/poteto-mode/references/codex-tools.md. Original pstack by Lauren Tan (poteto).", "author": { "name": "Michael Denyer", diff --git a/plugins/pstack/.github/plugin/plugin.json b/plugins/pstack/.github/plugin/plugin.json index d100dc3f..7485f358 100644 --- a/plugins/pstack/.github/plugin/plugin.json +++ b/plugins/pstack/.github/plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "pstack", "displayName": "pstack (GitHub Copilot port)", - "version": "0.9.76", + "version": "0.9.77", "description": "If you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence. Ported from cursor/plugins/pstack to GitHub Copilot CLI and app. Original pstack by Lauren Tan (poteto).", "author": { "name": "Michael Denyer", diff --git a/plugins/pstack/hooks/herdr-agent-gate.sh b/plugins/pstack/hooks/herdr-agent-gate.sh index 708af998..fe755e84 100755 --- a/plugins/pstack/hooks/herdr-agent-gate.sh +++ b/plugins/pstack/hooks/herdr-agent-gate.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # PreToolUse gate on the Agent tool. Inside Herdr, delegation goes through -# herdr-dispatch.ts; outside Herdr, or when the dispatcher cannot run, the +# herdr/dispatch.ts; outside Herdr, or when the dispatcher cannot run, the # native Agent tool stays available. set -u @@ -8,7 +8,7 @@ if [ "${HERDR_ENV:-}" != "1" ]; then exit 0; fi if [ "${PSTACK_HERDR_ALLOW_NATIVE_AGENT:-}" = "1" ]; then exit 0; fi plugin_root="${CLAUDE_PLUGIN_ROOT:-}" -dispatcher="${plugin_root}/skills/poteto-mode/scripts/herdr-dispatch.ts" +dispatcher="${plugin_root}/skills/poteto-mode/scripts/herdr/dispatch.ts" mapping="${plugin_root}/skills/poteto-mode/references/herdr-tools.md" json_string() { @@ -31,7 +31,7 @@ deny() { } if ! command -v herdr >/dev/null 2>&1; then - allow_with_note "HERDR_ENV=1 but herdr is not on PATH, so this delegation uses the native Agent tool. Put the herdr binary on PATH to route it through herdr-dispatch.ts." + allow_with_note "HERDR_ENV=1 but herdr is not on PATH, so this delegation uses the native Agent tool. Put the herdr binary on PATH to route it through herdr/dispatch.ts." fi bun_bin="$(command -v bun 2>/dev/null || true)" @@ -39,7 +39,7 @@ if [ -z "$bun_bin" ] && [ -x "${HOME:-}/.bun/bin/bun" ]; then bun_bin="${HOME:-}/.bun/bin/bun" fi if [ -z "$bun_bin" ]; then - allow_with_note "HERDR_ENV=1 but bun is not installed, so this delegation uses the native Agent tool. Install bun (https://bun.sh) to route it through herdr-dispatch.ts." + allow_with_note "HERDR_ENV=1 but bun is not installed, so this delegation uses the native Agent tool. Install bun (https://bun.sh) to route it through herdr/dispatch.ts." fi if [ ! -r "$dispatcher" ]; then diff --git a/plugins/pstack/skills/poteto-mode/references/herdr-tools.md b/plugins/pstack/skills/poteto-mode/references/herdr-tools.md index a6d1bfe5..a82c9a7f 100644 --- a/plugins/pstack/skills/poteto-mode/references/herdr-tools.md +++ b/plugins/pstack/skills/poteto-mode/references/herdr-tools.md @@ -1,6 +1,6 @@ # Herdr execution mapping -When `HERDR_ENV=1`, pstack delegation uses the executable `scripts/herdr-dispatch.ts`. Do not use Claude Code `Agent`/`Task` or Codex `spawn_agent` for a delegation that this dispatcher can represent. +When `HERDR_ENV=1`, pstack delegation uses the executable `scripts/herdr/dispatch.ts`. Do not use Claude Code `Agent`/`Task` or Codex `spawn_agent` for a delegation that this dispatcher can represent. This is a structural runtime rule, not a session-start hint. Skills and playbooks keep their native spawn language. This file is the translation, the same way `codex-tools.md` is the Codex translation. @@ -14,7 +14,7 @@ A `PreToolUse` hook on `Agent` (`herdr-agent-gate.sh` under the plugin hooks dir | pstack / Claude action | Herdr equivalent | |------------------------|------------------| -| Dispatch a subagent (`Agent` / `Task`) | `herdr-dispatch.ts` | +| Dispatch a subagent (`Agent` / `Task`) | `herdr/dispatch.ts` | | Dispatch N parallel subagents in one turn | N dispatcher processes launched concurrently without `--wait`, then `--next` | | `subagent_type` | ignored; pass the semantic pstack `--role` | | `model` | omit unless overriding; `--role` selects a profile from `~/.config/pstack-herdr/routes.yaml` | @@ -28,7 +28,7 @@ A `PreToolUse` hook on `Agent` (`herdr-agent-gate.sh` under the plugin hooks dir Write substantial worker prompts to a temporary file, then invoke: ```bash -bun <poteto-mode>/scripts/herdr-dispatch.ts \ +bun <poteto-mode>/scripts/herdr/dispatch.ts \ --role <role> \ --name <unique-agent-name> \ --prompt-file <brief.md> \ @@ -47,7 +47,7 @@ For parallel fan-out, launch dispatcher processes concurrently rather than dispa Never sleep and re-check a worker. Block on `--next` instead: ```bash -bun <poteto-mode>/scripts/herdr-dispatch.ts --next <name> [<name> ...] --timeout <ms> +bun <poteto-mode>/scripts/herdr/dispatch.ts --next <name> [<name> ...] --timeout <ms> ``` It returns as soon as any named worker settles. Herdr pushes each worker's status changes to it, and it reads a Codex worker's rollout every second. It reads and closes every worker that has settled, then prints `{"settled": [...], "pending": [...]}`. Each `settled` entry has the same fields as a `--collect` result, or `agent` and `error` when that worker could not be read. Act on each settled entry, then call `--next` again with the `pending` names until none are left. A budget that ends with nothing settled returns an empty `settled` and every name in `pending`. `--next` only watches. A stopped `--next` or one whose budget ends leaves every worker open, so a shell tool's own timeout loses no work. Pass each name to one `--next` at a time. @@ -85,7 +85,7 @@ With `--wait`, the dispatcher returns JSON containing the Herdr agent name, pane `blocked: true` is not completion. Inspect the worker in Herdr and resolve the approval or question. `unknown` is not proof of completion; it is the status Herdr reported. `working` means the wait budget ran out with the worker still running. The dispatcher leaves `working`, `blocked`, and `unknown` panes open. Finish a `working` worker through the dispatcher, not raw `herdr` commands, so its pane closes when it is done: ```bash -bun <poteto-mode>/scripts/herdr-dispatch.ts --collect --name <agent-name> --timeout <ms> +bun <poteto-mode>/scripts/herdr/dispatch.ts --collect --name <agent-name> --timeout <ms> ``` It returns the same settled JSON without `profile` and `depth`, and leaves the pane open again if the worker is still running when the budget ends. Close a pane with `herdr pane close <pane>` only when the work is no longer wanted. A dispatcher stopped by SIGTERM, SIGINT, or SIGHUP while it waits on a worker, in a dispatch or a `--collect`, closes that worker's pane before it exits; SIGKILL cannot be caught and still leaks the pane. diff --git a/plugins/pstack/skills/poteto-mode/scripts/herdr-config.test.ts b/plugins/pstack/skills/poteto-mode/scripts/herdr/config.test.ts similarity index 98% rename from plugins/pstack/skills/poteto-mode/scripts/herdr-config.test.ts rename to plugins/pstack/skills/poteto-mode/scripts/herdr/config.test.ts index 33ab48d3..64c907d0 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/herdr-config.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/herdr/config.test.ts @@ -9,7 +9,7 @@ import { renderRoutesYaml, writeRoutesAtomic, type RoutesConfig, -} from "./herdr-config.ts"; +} from "./config.ts"; const dirs: string[] = []; afterEach(() => { @@ -210,7 +210,7 @@ describe("herdr deterministic setup", () => { test("committed setup example is valid input", () => { const example = readFileSync( - join(import.meta.dir, "../../../../../config/setup.example.json"), + join(import.meta.dir, "../../../../../../config/setup.example.json"), "utf8" ); const config = buildRoutes({}, parseSetupInput(example)); @@ -264,7 +264,7 @@ async function runConfigure( args: string[] ): Promise<{ stdout: string; stderr: string; exitCode: number }> { const proc = Bun.spawn( - ["bun", join(import.meta.dir, "configure-herdr.ts"), ...args], + ["bun", join(import.meta.dir, "configure.ts"), ...args], { cwd: import.meta.dir, stdout: "pipe", diff --git a/plugins/pstack/skills/poteto-mode/scripts/herdr-config.ts b/plugins/pstack/skills/poteto-mode/scripts/herdr/config.ts similarity index 100% rename from plugins/pstack/skills/poteto-mode/scripts/herdr-config.ts rename to plugins/pstack/skills/poteto-mode/scripts/herdr/config.ts diff --git a/plugins/pstack/skills/poteto-mode/scripts/configure-herdr.ts b/plugins/pstack/skills/poteto-mode/scripts/herdr/configure.ts similarity index 95% rename from plugins/pstack/skills/poteto-mode/scripts/configure-herdr.ts rename to plugins/pstack/skills/poteto-mode/scripts/herdr/configure.ts index 4f93959b..7f300c21 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/configure-herdr.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/herdr/configure.ts @@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs"; import { resolve } from "node:path"; -import { ensureDependenciesInstalled } from "./bootstrap.ts"; +import { ensureDependenciesInstalled } from "../bootstrap.ts"; import { buildRoutes, expandHome, @@ -10,7 +10,7 @@ import { parseSetupInput, renderRoutesYaml, writeRoutesAtomic, -} from "./herdr-config.ts"; +} from "./config.ts"; interface Options { input: string; diff --git a/plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.test.ts b/plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.test.ts similarity index 99% rename from plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.test.ts rename to plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.test.ts index 044f95f2..802bf72f 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.test.ts @@ -9,7 +9,7 @@ import { import { createServer } from "node:net"; import { homedir, tmpdir } from "node:os"; import { join, resolve } from "node:path"; -import { loadRoutes, parseRoutes, type RoutesConfig } from "./herdr-config.ts"; +import { loadRoutes, parseRoutes, type RoutesConfig } from "./config.ts"; import { agentStatus, applyReadonlyPrompt, @@ -36,7 +36,7 @@ import { type DispatchOptions, type HerdrExec, type StatusWatch, -} from "./herdr-dispatch.ts"; +} from "./dispatch.ts"; const config: RoutesConfig = { profiles: { diff --git a/plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.ts b/plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.ts similarity index 99% rename from plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.ts rename to plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.ts index 0b4844c6..c03dfc02 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.ts @@ -11,7 +11,7 @@ import { import { connect, type Socket } from "node:net"; import { constants, homedir, tmpdir } from "node:os"; import { basename, dirname, join, resolve } from "node:path"; -import { ensureDependenciesInstalled } from "./bootstrap.ts"; +import { ensureDependenciesInstalled } from "../bootstrap.ts"; import { CONFIG_HOMES, expandHome, @@ -21,7 +21,7 @@ import { type AgentKind, type Profile, type RoutesConfig, -} from "./herdr-config.ts"; +} from "./config.ts"; export type AgentStatus = "idle" | "working" | "blocked" | "done" | "unknown"; diff --git a/plugins/pstack/skills/setup-pstack/SKILL.md b/plugins/pstack/skills/setup-pstack/SKILL.md index c73e2013..c7c42f21 100644 --- a/plugins/pstack/skills/setup-pstack/SKILL.md +++ b/plugins/pstack/skills/setup-pstack/SKILL.md @@ -21,7 +21,7 @@ Do not configure both layers unless the user explicitly wants native fallback be Herdr-backed delegation reads `~/.config/pstack-herdr/routes.yaml`. Do not hand-author that file. The deterministic writer is: ```bash -bun ../poteto-mode/scripts/configure-herdr.ts --input <setup.json> +bun ../poteto-mode/scripts/herdr/configure.ts --input <setup.json> ``` The setup skill owns discovery and user choices. The script owns validation, canonical serialization, preservation rules, atomic writes, and idempotency. @@ -122,7 +122,7 @@ Do not write YAML yourself. Preview the exact canonical YAML first: ```bash -bun ../poteto-mode/scripts/configure-herdr.ts \ +bun ../poteto-mode/scripts/herdr/configure.ts \ --input <setup.json> \ --dry-run ``` @@ -130,7 +130,7 @@ bun ../poteto-mode/scripts/configure-herdr.ts \ Then apply it: ```bash -bun ../poteto-mode/scripts/configure-herdr.ts \ +bun ../poteto-mode/scripts/herdr/configure.ts \ --input <setup.json> ``` diff --git a/tests/herdr-agent-gate.test.mjs b/tests/herdr-agent-gate.test.mjs index 307bb5c0..693d1636 100644 --- a/tests/herdr-agent-gate.test.mjs +++ b/tests/herdr-agent-gate.test.mjs @@ -19,8 +19,8 @@ function fixture({ herdr = false, bun = false, bunInHome = false, dispatcher = t const plugin = join(root, 'plugin'); mkdirSync(bin); mkdirSync(home); - mkdirSync(join(plugin, 'skills', 'poteto-mode', 'scripts'), { recursive: true }); - if (dispatcher) writeFileSync(join(plugin, 'skills', 'poteto-mode', 'scripts', 'herdr-dispatch.ts'), ''); + mkdirSync(join(plugin, 'skills', 'poteto-mode', 'scripts', 'herdr'), { recursive: true }); + if (dispatcher) writeFileSync(join(plugin, 'skills', 'poteto-mode', 'scripts', 'herdr', 'dispatch.ts'), ''); const stub = (path) => { writeFileSync(path, '#!/bin/sh\nexit 0\n'); chmodSync(path, 0o755); @@ -53,10 +53,10 @@ test('inside Herdr with herdr and bun on PATH the Agent tool is denied', () => { const decision = out.hookSpecificOutput; assert.equal(decision.hookEventName, 'PreToolUse'); assert.equal(decision.permissionDecision, 'deny'); - assert.ok(decision.permissionDecisionReason.includes(`${fx.bin}/bun ${fx.plugin}/skills/poteto-mode/scripts/herdr-dispatch.ts`)); + assert.ok(decision.permissionDecisionReason.includes(`${fx.bin}/bun ${fx.plugin}/skills/poteto-mode/scripts/herdr/dispatch.ts`)); assert.ok(decision.permissionDecisionReason.includes(`${fx.plugin}/skills/poteto-mode/references/herdr-tools.md`)); assert.match(decision.permissionDecisionReason, /restart Claude Code with PSTACK_HERDR_ALLOW_NATIVE_AGENT=1/); - assert.ok(decision.permissionDecisionReason.includes(`${fx.bin}/bun ${fx.plugin}/skills/poteto-mode/scripts/herdr-dispatch.ts --next <agent-name>`)); + assert.ok(decision.permissionDecisionReason.includes(`${fx.bin}/bun ${fx.plugin}/skills/poteto-mode/scripts/herdr/dispatch.ts --next <agent-name>`)); assert.match(decision.permissionDecisionReason, /never sleep and re-check/); }); @@ -64,7 +64,7 @@ test('a bun that lives only in ~/.bun/bin is named by full path', () => { const fx = fixture({ herdr: true, bunInHome: true }); const out = run({ HERDR_ENV: '1' }, fx); assert.equal(out.hookSpecificOutput.permissionDecision, 'deny'); - assert.ok(out.hookSpecificOutput.permissionDecisionReason.includes(`${fx.home}/.bun/bin/bun ${fx.plugin}/skills/poteto-mode/scripts/herdr-dispatch.ts`)); + assert.ok(out.hookSpecificOutput.permissionDecisionReason.includes(`${fx.home}/.bun/bin/bun ${fx.plugin}/skills/poteto-mode/scripts/herdr/dispatch.ts`)); }); test('a missing dispatcher allows the Agent tool with a note instead of a dead command', () => { diff --git a/tests/herdr-e2e.sh b/tests/herdr-e2e.sh index 616b1183..e09e3a95 100644 --- a/tests/herdr-e2e.sh +++ b/tests/herdr-e2e.sh @@ -31,7 +31,7 @@ export HERDR_AGENT=claude herdr pane report-agent "$HERDR_PANE_ID" \ --source custom:pstack-herdr-ci --agent claude --state idle >/dev/null -# herdr-dispatch.ts believes a done/idle verdict only once a transcript under +# herdr/dispatch.ts believes a done/idle verdict only once a transcript under # $CLAUDE_CONFIG_DIR/projects records the prompt (turnEvidence), so this stand-in # writes one after each prompt, the same shape a real Claude Code session writes. transcript_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/projects/pstack-herdr-e2e" @@ -80,7 +80,7 @@ if [[ -z "$pane" || "$pane" == "null" ]]; then exit 1 fi -command="HERDR_ENV=1 PSTACK_HERDR_PARENT_KIND=claude PATH='$PATH' bun '$ROOT/plugins/pstack/skills/poteto-mode/scripts/herdr-dispatch.ts' --role explorer --name ci-explorer --kind claude --cwd '$ROOT' --prompt 'pstack-herdr-e2e' --wait --timeout 15000 > '$RESULT' 2>&1; code=\$?; echo PSTACK_HERDR_E2E_EXIT:\$code" +command="HERDR_ENV=1 PSTACK_HERDR_PARENT_KIND=claude PATH='$PATH' bun '$ROOT/plugins/pstack/skills/poteto-mode/scripts/herdr/dispatch.ts' --role explorer --name ci-explorer --kind claude --cwd '$ROOT' --prompt 'pstack-herdr-e2e' --wait --timeout 15000 > '$RESULT' 2>&1; code=\$?; echo PSTACK_HERDR_E2E_EXIT:\$code" herdr pane run "$pane" "$command" >/dev/null herdr pane wait-output "$pane" --regex 'PSTACK_HERDR_E2E_EXIT:[0-9]+' --timeout 30000 >/dev/null diff --git a/tests/herdr-runtime.test.mjs b/tests/herdr-runtime.test.mjs index 98153252..7cf1d89d 100644 --- a/tests/herdr-runtime.test.mjs +++ b/tests/herdr-runtime.test.mjs @@ -51,7 +51,7 @@ if (!agentGate) throw new Error("hooks.json must gate the Agent tool with herdr- if (!mapping.includes("herdr-agent-gate.sh") || !mapping.includes("PSTACK_HERDR_ALLOW_NATIVE_AGENT")) { throw new Error("Herdr mapping does not document the Agent gate and its bypass"); } -if (!mapping.includes("herdr-dispatch.ts")) throw new Error("Herdr mapping does not point to dispatcher"); +if (!mapping.includes("herdr/dispatch.ts")) throw new Error("Herdr mapping does not point to dispatcher"); if (!mapping.includes("| `how` |") || !mapping.includes("| Feature / bug-fix")) { throw new Error("Herdr mapping is missing per-skill notes"); } @@ -68,14 +68,14 @@ for (const skill of skills) { if (!body.includes("herdr-tools.md")) { throw new Error(`${skill} does not point at the Herdr mapping`); } - if (body.includes("Under Herdr") || body.includes("Outside Herdr") || body.includes("herdr-dispatch")) { + if (body.includes("Under Herdr") || body.includes("Outside Herdr") || body.includes("herdr/dispatch")) { throw new Error(`${skill} dual-paths Herdr instead of using the mapping`); } } for (const playbook of playbooks) { const body = read(`plugins/pstack/skills/poteto-mode/playbooks/${playbook}.md`); if ( - body.includes("herdr-dispatch") || + body.includes("herdr/dispatch") || body.includes("Under Herdr") || body.includes("Outside Herdr") || body.includes("HERDR_ENV") @@ -107,7 +107,7 @@ if (routes.includes("workspace:") || routes.includes("round-robin") || routes.in if (!setup.includes("If `HERDR_ENV=1`, follow **Herdr setup**")) { throw new Error("setup-pstack does not branch on Herdr runtime"); } -if (!setup.includes("configure-herdr.ts") || !setup.includes("Do not write YAML yourself.")) { +if (!setup.includes("herdr/configure.ts") || !setup.includes("Do not write YAML yourself.")) { throw new Error("setup-pstack does not delegate Herdr config writes to the deterministic script"); } if (!setup.includes("~/.config/pstack-herdr/routes.yaml")) {