@mcuste/pi-herdr-worktree 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mcuste
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # pi-herdr-worktree
2
+
3
+ [![CI](https://github.com/mcuste/pi-herdr-worktree/actions/workflows/ci.yml/badge.svg)](https://github.com/mcuste/pi-herdr-worktree/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/@mcuste/pi-herdr-worktree)](https://www.npmjs.com/package/@mcuste/pi-herdr-worktree)
5
+
6
+ An extension for the [Pi](https://github.com/earendil-works/pi) and
7
+ [Oh My Pi](https://github.com/can1357/oh-my-pi) terminal coding agents. It gives the model one
8
+ `herdr_worktree` tool that runs a fixed set of [Herdr](https://herdr.dev) Git worktree operations,
9
+ instead of letting it type `herdr` commands into a shell.
10
+
11
+ If any of those names are new to you:
12
+
13
+ - **Herdr** is a terminal workspace manager for coding agents. It can back a workspace with a Git
14
+ worktree, so a branch gets its own checkout, its own tabs, and its own panes.
15
+ - **Pi** and **Oh My Pi** are terminal coding agents. An **extension** is an npm package they load
16
+ at startup to add tools the model can call.
17
+
18
+ ## Why
19
+
20
+ An agent with shell access can already run `herdr`, but a shell gives it a free-form command
21
+ string. Herdr's CLI does not accept `--flag=value`, so every value is a separate argument, and a
22
+ value that starts with `-` silently becomes a flag. A wrong `--workspace` deletes the wrong
23
+ checkout, `--force` throws away uncommitted work, and `--focus` moves the user's view away from
24
+ what they were doing.
25
+
26
+ This extension replaces that with a closed set of operations. Each one accepts only its own typed
27
+ fields, builds a fixed argument array, checks the repository before running, and verifies the
28
+ result against Git afterwards. There is no shell string anywhere.
29
+
30
+ ## Requirements
31
+
32
+ - Node.js 22 or newer
33
+ - Git 2.36 or newer
34
+ - Herdr 0.8.2 or newer, and an agent running inside a Herdr-managed pane
35
+
36
+ The extension loads anywhere. Outside a Herdr pane, outside a Git work tree, or without the
37
+ `herdr` CLI, a tool call fails with an explanatory error rather than running an unsupported
38
+ command.
39
+
40
+ ## Install
41
+
42
+ Pi:
43
+
44
+ ```bash
45
+ pi install npm:@mcuste/pi-herdr-worktree
46
+ ```
47
+
48
+ Oh My Pi:
49
+
50
+ ```bash
51
+ omp plugin install @mcuste/pi-herdr-worktree
52
+ ```
53
+
54
+ Or through the Oh My Pi marketplace:
55
+
56
+ ```text
57
+ /marketplace add mcuste/pi-herdr-worktree
58
+ /marketplace install pi-herdr-worktree@pi-herdr-worktree
59
+ ```
60
+
61
+ From a local checkout:
62
+
63
+ ```bash
64
+ pnpm install
65
+ pi install /absolute/path/to/pi-herdr-worktree
66
+ omp plugin link /absolute/path/to/pi-herdr-worktree
67
+ ```
68
+
69
+ ## What the tool does
70
+
71
+ One tool named `herdr_worktree`, selected by an `operation` field:
72
+
73
+ | Operation | Runs | Purpose |
74
+ | --- | --- | --- |
75
+ | `list` | `herdr worktree list` | Read every worktree of the current repository |
76
+ | `create` | `herdr worktree create` | Add a worktree on a new branch and open it as a workspace |
77
+ | `open` | `herdr worktree open` | Open an existing worktree as a workspace |
78
+ | `remove` | `herdr worktree remove` | Delete a worktree checkout and close its workspace |
79
+
80
+ `list` is read-only. The other three change the repository and the session, and Oh My Pi asks for
81
+ execute approval; `remove` always prompts, and so does a `create` that names its own `path`. Full
82
+ per-operation fields and guarantees are in [docs/operations.md](docs/operations.md).
83
+
84
+ ## System prompt note
85
+
86
+ The tool loads on demand, so its description is not in the base prompt. The model can reach for
87
+ `herdr` or `git worktree` in a shell before it sees the tool, so the extension appends one note to
88
+ the system prompt at the start of each turn:
89
+
90
+ > Use the herdr_worktree tool for Git worktrees, not herdr or git worktree in bash. Create a
91
+ > worktree when work needs its own checkout, for example parallel multi-agent work.
92
+
93
+ The note is added once per prompt, only where detection succeeds, and only in a host that offers
94
+ the `before_agent_start` event. A host without that event still gets the tool.
95
+
96
+ Detection reads `HERDR_ENV` first. That variable belongs to the process, so outside Herdr the
97
+ extension starts no child process at all. Inside a Herdr pane, detection also finds the Git
98
+ repository and reads the `herdr` version, then caches the result in memory for ten minutes. A
99
+ failed detection is cached for five minutes, so a directory that is not a Git work tree does not
100
+ run `git` and `herdr` on every turn.
101
+
102
+ ## What is deliberately missing
103
+
104
+ - **Workspace, tab, pane, and agent control** (`herdr workspace`, `herdr tab`, `herdr pane`,
105
+ `herdr agent`) is a separate concern. This extension only manages worktree-backed workspaces.
106
+ - **Session and server control** (`herdr session`, `herdr server`, `herdr update`) can stop the
107
+ user's session and every process in it.
108
+ - **`herdr worktree remove` without a verified target.** The tool always lists the repository's
109
+ worktrees first and refuses a workspace that does not hold a linked worktree of it.
110
+
111
+ For anything else, the model works through the normal Herdr workflow with the user in the loop.
112
+
113
+ ## Safety
114
+
115
+ Every operation checks the repository before and after it runs: a new worktree must be registered
116
+ by Git on the requested branch, an opened worktree must be the one that was asked for, and a
117
+ removed checkout must be gone from `git worktree list`. A failed check is reported as an uncertain
118
+ result rather than retried.
119
+
120
+ Arguments are never concatenated into a shell command. Herdr does not accept `--flag=value`, so
121
+ every value travels as its own argument and is rejected before spawning if it starts with `-`. Each
122
+ value also carries the option it was parsed for, so a label cannot arrive as a branch. See
123
+ [docs/safety.md](docs/safety.md).
124
+
125
+ Focus defaults to off. The tool passes `--no-focus` unless the caller explicitly asks to move the
126
+ user's view.
127
+
128
+ ## Documentation
129
+
130
+ - [Operations reference](docs/operations.md)
131
+ - [Safety model](docs/safety.md)
132
+ - [Development and release](docs/development.md)
133
+ - [Changelog](CHANGELOG.md)
134
+
135
+ ## License
136
+
137
+ [MIT](LICENSE)
@@ -0,0 +1,54 @@
1
+ import { type CommandRunner } from "./process.js";
2
+ export declare const MAX_BRANCH_NAME_LENGTH = 255;
3
+ export declare const MAX_PATH_LENGTH = 4096;
4
+ export declare const MAX_LABEL_LENGTH = 120;
5
+ export declare const MIN_WORKSPACE_ID_LENGTH = 2;
6
+ export declare const MAX_WORKSPACE_ID_LENGTH = 17;
7
+ declare const branchNameBrand: unique symbol;
8
+ declare const revisionBrand: unique symbol;
9
+ declare const commitIdBrand: unique symbol;
10
+ declare const absolutePathBrand: unique symbol;
11
+ declare const labelBrand: unique symbol;
12
+ declare const workspaceIdBrand: unique symbol;
13
+ /** The branch name `git check-ref-format --branch` printed, so shorthand is already expanded. */
14
+ export type ParsedBranchName = string & {
15
+ readonly [branchNameBrand]: true;
16
+ };
17
+ /** A revision that `git rev-parse --verify` resolved to a commit. */
18
+ export type ParsedRevision = string & {
19
+ readonly [revisionBrand]: true;
20
+ };
21
+ /** A full object id that `git rev-parse` printed. */
22
+ type ParsedCommitId = string & {
23
+ readonly [commitIdBrand]: true;
24
+ };
25
+ export type ParsedAbsolutePath = string & {
26
+ readonly [absolutePathBrand]: true;
27
+ };
28
+ export type ParsedLabel = string & {
29
+ readonly [labelBrand]: true;
30
+ };
31
+ export type ParsedWorkspaceId = string & {
32
+ readonly [workspaceIdBrand]: true;
33
+ };
34
+ /**
35
+ * Every value that may become a command argument. A plain string is not a member, so unparsed
36
+ * text cannot reach a spawned command.
37
+ */
38
+ export type ParsedArgumentValue = ParsedAbsolutePath | ParsedBranchName | ParsedLabel | ParsedRevision | ParsedWorkspaceId;
39
+ /** A revision, together with the commit it pointed at when it was parsed. */
40
+ export interface ParsedBase {
41
+ readonly revision: ParsedRevision;
42
+ readonly commit: ParsedCommitId;
43
+ }
44
+ /** The runtime re-check of what the parsed types already promise, for the gate before a spawn. */
45
+ export declare function hasParsedArgumentSyntax(value: unknown): value is string;
46
+ export declare function parseBranchName(runner: CommandRunner, value: unknown, label: string, cwd: string, signal: AbortSignal | undefined): Promise<ParsedBranchName>;
47
+ /** A base for a new worktree only has to resolve to a commit, so tags and SHAs are allowed. */
48
+ export declare function parseBase(runner: CommandRunner, value: unknown, label: string, cwd: string, signal: AbortSignal | undefined): Promise<ParsedBase>;
49
+ export declare function parseAbsolutePath(value: unknown, label: string): ParsedAbsolutePath;
50
+ /** A label is display text, so it only has to stay a single argument on one line. */
51
+ export declare function parseLabel(value: unknown, label: string): ParsedLabel;
52
+ export declare function parseWorkspaceId(value: unknown, label: string): ParsedWorkspaceId;
53
+ export {};
54
+ //# sourceMappingURL=argument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"argument.d.ts","sourceRoot":"","sources":["../src/argument.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,cAAc,CAAC;AAEtB,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAIpC,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C,OAAO,CAAC,MAAM,eAAe,EAAE,OAAO,MAAM,CAAC;AAC7C,OAAO,CAAC,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC;AAC3C,OAAO,CAAC,MAAM,aAAa,EAAE,OAAO,MAAM,CAAC;AAC3C,OAAO,CAAC,MAAM,iBAAiB,EAAE,OAAO,MAAM,CAAC;AAC/C,OAAO,CAAC,MAAM,UAAU,EAAE,OAAO,MAAM,CAAC;AACxC,OAAO,CAAC,MAAM,gBAAgB,EAAE,OAAO,MAAM,CAAC;AAE9C,iGAAiG;AACjG,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,eAAe,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAE7E,qEAAqE;AACrE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAEzE,qDAAqD;AACrD,KAAK,cAAc,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAElE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,iBAAiB,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAEjF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,UAAU,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAC3B,kBAAkB,GAClB,gBAAgB,GAChB,WAAW,GACX,cAAc,GACd,iBAAiB,CAAC;AAEtB,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC;AAsBD,kGAAkG;AAClG,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEvE;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CA6B3B;AAED,+FAA+F;AAC/F,wBAAsB,SAAS,CAC7B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAYnF;AAED,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAKrE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAKjF"}
@@ -0,0 +1,101 @@
1
+ import { isAbsolute, normalize } from "node:path";
2
+ import { CommandCancelledError, runChecked, } from "./process.js";
3
+ export const MAX_BRANCH_NAME_LENGTH = 255;
4
+ export const MAX_PATH_LENGTH = 4096;
5
+ export const MAX_LABEL_LENGTH = 120;
6
+ /** The bounds come from the pattern, so the schema advertises exactly what this accepts. */
7
+ const WORKSPACE_ID = /^w[0-9A-Za-z]{1,16}$/u;
8
+ export const MIN_WORKSPACE_ID_LENGTH = 2;
9
+ export const MAX_WORKSPACE_ID_LENGTH = 17;
10
+ /** Bidirectional marks make text display as other text, and a newline forges a line of output. */
11
+ const UNSAFE_CHARACTERS = /[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/u;
12
+ const COMMIT_ID = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/u;
13
+ /**
14
+ * Rejects values that could turn into a command-line option, split an argument, or forge a line
15
+ * of output before Git or Herdr ever sees them.
16
+ */
17
+ function hasSafeArgumentSyntax(value, maxLength) {
18
+ return (typeof value === "string" &&
19
+ value.length > 0 &&
20
+ value.length <= maxLength &&
21
+ value === value.trim() &&
22
+ !value.startsWith("-") &&
23
+ !UNSAFE_CHARACTERS.test(value));
24
+ }
25
+ /** The runtime re-check of what the parsed types already promise, for the gate before a spawn. */
26
+ export function hasParsedArgumentSyntax(value) {
27
+ return hasSafeArgumentSyntax(value, MAX_PATH_LENGTH);
28
+ }
29
+ export async function parseBranchName(runner, value, label, cwd, signal) {
30
+ if (!hasSafeArgumentSyntax(value, MAX_BRANCH_NAME_LENGTH)) {
31
+ throw new Error(`${label} is not a safe branch name: ${JSON.stringify(value)}.`);
32
+ }
33
+ let result;
34
+ try {
35
+ result = await runChecked(runner, "git", ["check-ref-format", "--branch", value], { cwd, signal }, `${label} is not a valid Git branch name`);
36
+ }
37
+ catch (error) {
38
+ if (error instanceof CommandCancelledError) {
39
+ throw error;
40
+ }
41
+ throw new Error(`${label} is not a valid Git branch name: ${JSON.stringify(value)}.`, {
42
+ cause: error,
43
+ });
44
+ }
45
+ // `--branch` expands shorthand such as `@{-1}`, so only the printed name names the real branch.
46
+ const expanded = result.stdout.trim();
47
+ if (!hasSafeArgumentSyntax(expanded, MAX_BRANCH_NAME_LENGTH)) {
48
+ throw new Error(`${label} did not expand to a safe branch name: ${JSON.stringify(value)}.`);
49
+ }
50
+ return expanded;
51
+ }
52
+ /** A base for a new worktree only has to resolve to a commit, so tags and SHAs are allowed. */
53
+ export async function parseBase(runner, value, label, cwd, signal) {
54
+ if (!hasSafeArgumentSyntax(value, MAX_BRANCH_NAME_LENGTH)) {
55
+ throw new Error(`${label} is not a safe Git revision: ${JSON.stringify(value)}.`);
56
+ }
57
+ let result;
58
+ try {
59
+ result = await runChecked(runner, "git", ["rev-parse", "--verify", "--quiet", `${value}^{commit}`], { cwd, signal }, `${label} does not resolve to a commit`);
60
+ }
61
+ catch (error) {
62
+ if (error instanceof CommandCancelledError) {
63
+ throw error;
64
+ }
65
+ throw new Error(`${label} does not resolve to a commit: ${JSON.stringify(value)}.`, {
66
+ cause: error,
67
+ });
68
+ }
69
+ const commit = result.stdout.trim();
70
+ if (!COMMIT_ID.test(commit)) {
71
+ throw new Error(`${label} did not resolve to a commit id: ${JSON.stringify(value)}.`);
72
+ }
73
+ return { revision: value, commit: commit };
74
+ }
75
+ export function parseAbsolutePath(value, label) {
76
+ if (!hasSafeArgumentSyntax(value, MAX_PATH_LENGTH)) {
77
+ throw new Error(`${label} is not a safe path: ${JSON.stringify(value)}.`);
78
+ }
79
+ if (!isAbsolute(value)) {
80
+ throw new Error(`${label} must be an absolute path: ${JSON.stringify(value)}.`);
81
+ }
82
+ const normalized = normalize(value).replace(/\/+$/u, "");
83
+ if (!normalized || !isAbsolute(normalized)) {
84
+ throw new Error(`${label} does not normalise to an absolute path: ${JSON.stringify(value)}.`);
85
+ }
86
+ return normalized;
87
+ }
88
+ /** A label is display text, so it only has to stay a single argument on one line. */
89
+ export function parseLabel(value, label) {
90
+ if (!hasSafeArgumentSyntax(value, MAX_LABEL_LENGTH)) {
91
+ throw new Error(`${label} must be single-line text without leading options.`);
92
+ }
93
+ return value;
94
+ }
95
+ export function parseWorkspaceId(value, label) {
96
+ if (!hasSafeArgumentSyntax(value, MAX_WORKSPACE_ID_LENGTH) || !WORKSPACE_ID.test(value)) {
97
+ throw new Error(`${label} is not a Herdr workspace id: ${JSON.stringify(value)}.`);
98
+ }
99
+ return value;
100
+ }
101
+ //# sourceMappingURL=argument.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"argument.js","sourceRoot":"","sources":["../src/argument.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { type ParsedAbsolutePath } from "./argument.js";
2
+ import { type CommandRunner } from "./process.js";
3
+ export interface GitRepository {
4
+ readonly root: ParsedAbsolutePath;
5
+ readonly gitDir: ParsedAbsolutePath;
6
+ }
7
+ export interface HerdrCapability {
8
+ readonly repository: GitRepository;
9
+ readonly herdrVersion: string;
10
+ readonly cache: "memory" | "detected";
11
+ }
12
+ export declare class HerdrUnavailableError extends Error {
13
+ constructor(message: string, options?: ErrorOptions);
14
+ }
15
+ /**
16
+ * The capability is a property of the running session, not of the repository, so it is only
17
+ * held in memory. Nothing is written to the repository.
18
+ */
19
+ export declare class HerdrCapabilityResolver {
20
+ #private;
21
+ constructor(runner?: CommandRunner, environment?: Readonly<Record<string, string | undefined>>, now?: () => number);
22
+ ensure(cwd: string, signal?: AbortSignal): Promise<HerdrCapability>;
23
+ /**
24
+ * Never throws, unlike `ensure`. A failed detection is cached for a short time, so a caller that
25
+ * runs on every agent start does not spawn `git` and `herdr` every turn.
26
+ */
27
+ tryEnsure(cwd: string, signal?: AbortSignal): Promise<HerdrCapability | undefined>;
28
+ forget(cwd: string): void;
29
+ }
30
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,eAAe,CAAC;AAC3E,OAAO,EAGL,KAAK,aAAa,EAGnB,MAAM,cAAc,CAAC;AAStB,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;CACrC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,UAAU,CAAC;CACvC;AAOD,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAGlD;CACF;AA6BD;;;GAGG;AACH,qBAAa,uBAAuB;;IAOlC,YACE,MAAM,GAAE,aAA0B,EAClC,WAAW,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,EACvE,GAAG,GAAE,MAAM,MAAiB,EAK7B;IAEK,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAcxE;IAED;;;OAGG;IACG,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAqBvF;IAED,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAWxB;CAgFF"}
@@ -0,0 +1,154 @@
1
+ import { resolve } from "node:path";
2
+ import { parseAbsolutePath } from "./argument.js";
3
+ import { CommandCancelledError, runChecked, runCommand, } from "./process.js";
4
+ /** Herdr injects this into every managed pane. Without it there is no session to talk to. */
5
+ const SESSION_MARKER = "HERDR_ENV";
6
+ const MINIMUM_HERDR_VERSION = [0, 8, 2];
7
+ const CAPABILITY_MAX_AGE_MS = 10 * 60 * 1000;
8
+ const UNAVAILABLE_RETRY_MS = 5 * 60 * 1000;
9
+ export class HerdrUnavailableError extends Error {
10
+ constructor(message, options) {
11
+ super(message, options);
12
+ this.name = "HerdrUnavailableError";
13
+ }
14
+ }
15
+ function isAtLeast(installed, minimum) {
16
+ for (const [index, required] of minimum.entries()) {
17
+ const part = installed[index] ?? 0;
18
+ if (part !== required) {
19
+ return part > required;
20
+ }
21
+ }
22
+ return true;
23
+ }
24
+ function parseHerdrVersion(result) {
25
+ const raw = result.stdout.trim() || result.stderr.trim();
26
+ const match = /^(?:herdr )?(\d+)\.(\d+)\.(\d+)/u.exec(raw);
27
+ if (!match) {
28
+ throw new HerdrUnavailableError(`Unable to read the Herdr version from ${JSON.stringify(raw)}.`);
29
+ }
30
+ const parts = match.slice(1, 4).map(Number);
31
+ if (!isAtLeast(parts, MINIMUM_HERDR_VERSION)) {
32
+ throw new HerdrUnavailableError(`Herdr ${MINIMUM_HERDR_VERSION.join(".")} or newer is required, but ${parts.join(".")} is installed.`);
33
+ }
34
+ return parts.join(".");
35
+ }
36
+ /**
37
+ * The capability is a property of the running session, not of the repository, so it is only
38
+ * held in memory. Nothing is written to the repository.
39
+ */
40
+ export class HerdrCapabilityResolver {
41
+ #runner;
42
+ #environment;
43
+ #now;
44
+ #memory = new Map();
45
+ #unavailable = new Map();
46
+ constructor(runner = runCommand, environment = process.env, now = Date.now) {
47
+ this.#runner = runner;
48
+ this.#environment = environment;
49
+ this.#now = now;
50
+ }
51
+ async ensure(cwd, signal) {
52
+ const lookupKey = resolve(cwd);
53
+ const remembered = this.#memory.get(lookupKey);
54
+ if (remembered && remembered.expiresAt > this.#now()) {
55
+ return { ...remembered.capability, cache: "memory" };
56
+ }
57
+ this.#memory.delete(lookupKey);
58
+ this.#assertManagedSession();
59
+ const repository = await this.#findRepository(cwd, signal);
60
+ const herdrVersion = await this.#detectHerdr(repository, signal);
61
+ const capability = { repository, herdrVersion };
62
+ this.#remember(lookupKey, capability);
63
+ return { ...capability, cache: "detected" };
64
+ }
65
+ /**
66
+ * Never throws, unlike `ensure`. A failed detection is cached for a short time, so a caller that
67
+ * runs on every agent start does not spawn `git` and `herdr` every turn.
68
+ */
69
+ async tryEnsure(cwd, signal) {
70
+ // The marker comes from the process environment, so the answer cannot change while it runs.
71
+ if (!this.#isManagedSession()) {
72
+ return undefined;
73
+ }
74
+ const lookupKey = resolve(cwd);
75
+ const retryAt = this.#unavailable.get(lookupKey);
76
+ if (retryAt !== undefined && retryAt > this.#now()) {
77
+ return undefined;
78
+ }
79
+ this.#unavailable.delete(lookupKey);
80
+ try {
81
+ return await this.ensure(cwd, signal);
82
+ }
83
+ catch (error) {
84
+ if (error instanceof HerdrUnavailableError) {
85
+ this.#unavailable.set(lookupKey, this.#now() + UNAVAILABLE_RETRY_MS);
86
+ }
87
+ return undefined;
88
+ }
89
+ }
90
+ forget(cwd) {
91
+ const lookupKey = resolve(cwd);
92
+ this.#unavailable.delete(lookupKey);
93
+ const remembered = this.#memory.get(lookupKey);
94
+ const repositoryRoot = resolve(remembered?.capability.repository.root ?? cwd);
95
+ for (const [key, entry] of this.#memory) {
96
+ if (key === lookupKey || resolve(entry.capability.repository.root) === repositoryRoot) {
97
+ this.#memory.delete(key);
98
+ }
99
+ }
100
+ this.#unavailable.delete(repositoryRoot);
101
+ }
102
+ #remember(lookupKey, capability) {
103
+ const remembered = { capability, expiresAt: this.#now() + CAPABILITY_MAX_AGE_MS };
104
+ this.#memory.set(lookupKey, remembered);
105
+ this.#memory.set(resolve(capability.repository.root), remembered);
106
+ }
107
+ #isManagedSession() {
108
+ return this.#environment[SESSION_MARKER] === "1";
109
+ }
110
+ #assertManagedSession() {
111
+ if (!this.#isManagedSession()) {
112
+ throw new HerdrUnavailableError(`Herdr worktree tools only run inside a Herdr-managed pane, where ${SESSION_MARKER} is 1. Start the agent from a Herdr session and retry.`);
113
+ }
114
+ }
115
+ async #findRepository(cwd, signal) {
116
+ let result;
117
+ try {
118
+ result = await runChecked(this.#runner, "git", ["rev-parse", "--show-toplevel", "--absolute-git-dir"], { cwd, signal }, "Unable to locate the Git repository");
119
+ }
120
+ catch (error) {
121
+ if (error instanceof CommandCancelledError) {
122
+ throw error;
123
+ }
124
+ throw new HerdrUnavailableError("Herdr worktree actions require a Git work tree. Run the tool from inside a Git repository.", { cause: error });
125
+ }
126
+ const lines = result.stdout.replace(/\r?\n$/u, "").split(/\r?\n/u);
127
+ if (lines.length !== 2) {
128
+ throw new HerdrUnavailableError("Git returned an invalid repository location; Herdr worktree actions cannot run reliably here.");
129
+ }
130
+ try {
131
+ return {
132
+ root: parseAbsolutePath(lines[0], "The repository root"),
133
+ gitDir: parseAbsolutePath(lines[1], "The Git directory"),
134
+ };
135
+ }
136
+ catch (error) {
137
+ throw new HerdrUnavailableError("Git returned an invalid repository location; Herdr worktree actions cannot run reliably here.", { cause: error });
138
+ }
139
+ }
140
+ async #detectHerdr(repository, signal) {
141
+ let result;
142
+ try {
143
+ result = await runChecked(this.#runner, "herdr", ["--version"], { cwd: repository.root, signal }, "Unable to read the Herdr version");
144
+ }
145
+ catch (error) {
146
+ if (error instanceof CommandCancelledError) {
147
+ throw error;
148
+ }
149
+ throw new HerdrUnavailableError("The Herdr CLI is unavailable. Install `herdr` and ensure it is on PATH.", { cause: error });
150
+ }
151
+ return parseHerdrVersion(result);
152
+ }
153
+ }
154
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { HerdrCapabilityResolver } from "./capability.js";
2
+ /**
3
+ * The tool loads on demand, so the model needs this note before it reaches for `herdr` or
4
+ * `git worktree` in bash.
5
+ */
6
+ export declare const HERDR_PROMPT_NOTE = "Use the herdr_worktree tool for Git worktrees, not herdr or git worktree in bash. Create a worktree when work needs its own checkout, for example parallel multi-agent work.";
7
+ interface BeforeAgentStartEvent {
8
+ readonly systemPrompt?: unknown;
9
+ }
10
+ interface BeforeAgentStartContext {
11
+ readonly cwd?: string;
12
+ readonly signal?: AbortSignal;
13
+ }
14
+ interface BeforeAgentStartResult {
15
+ readonly systemPrompt: string;
16
+ }
17
+ export interface HerdrPromptApi {
18
+ on?(event: "before_agent_start", handler: (event: BeforeAgentStartEvent, context?: BeforeAgentStartContext) => Promise<BeforeAgentStartResult | undefined>): void;
19
+ }
20
+ export declare function registerHerdrGuidance(pi: HerdrPromptApi, capabilities: HerdrCapabilityResolver): void;
21
+ export {};
22
+ //# sourceMappingURL=guidance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guidance.d.ts","sourceRoot":"","sources":["../src/guidance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE/D;;;GAGG;AACH,eAAO,MAAM,iBAAiB,iLACkJ,CAAC;AAEjL,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,UAAU,uBAAuB;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,CACD,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,CACP,KAAK,EAAE,qBAAqB,EAC5B,OAAO,CAAC,EAAE,uBAAuB,KAC9B,OAAO,CAAC,sBAAsB,GAAG,SAAS,CAAC,GAC/C,IAAI,CAAC;CACT;AAED,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,cAAc,EAClB,YAAY,EAAE,uBAAuB,GACpC,IAAI,CAeN"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The tool loads on demand, so the model needs this note before it reaches for `herdr` or
3
+ * `git worktree` in bash.
4
+ */
5
+ export const HERDR_PROMPT_NOTE = "Use the herdr_worktree tool for Git worktrees, not herdr or git worktree in bash. Create a worktree when work needs its own checkout, for example parallel multi-agent work.";
6
+ export function registerHerdrGuidance(pi, capabilities) {
7
+ pi.on?.("before_agent_start", async (event, context) => {
8
+ const systemPrompt = event?.systemPrompt;
9
+ // A host may chain several handlers, so the note must not be appended twice.
10
+ if (typeof systemPrompt !== "string" || systemPrompt.includes(HERDR_PROMPT_NOTE)) {
11
+ return undefined;
12
+ }
13
+ const capability = await capabilities.tryEnsure(context?.cwd ?? process.cwd(), context?.signal);
14
+ if (!capability) {
15
+ return undefined;
16
+ }
17
+ return { systemPrompt: `${systemPrompt}\n\n${HERDR_PROMPT_NOTE}` };
18
+ });
19
+ }
20
+ //# sourceMappingURL=guidance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guidance.js","sourceRoot":"","sources":["../src/guidance.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,8KAA8K,CAAC;AAyBjL,MAAM,UAAU,qBAAqB,CACnC,EAAkB,EAClB,YAAqC;IAErC,EAAE,CAAC,EAAE,EAAE,CAAC,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QACrD,MAAM,YAAY,GAAG,KAAK,EAAE,YAAY,CAAC;QACzC,6EAA6E;QAC7E,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACjF,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAChG,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,EAAE,YAAY,EAAE,GAAG,YAAY,OAAO,iBAAiB,EAAE,EAAE,CAAC;IACrE,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type HerdrExtensionApi } from "./tools.js";
2
+ export default function piHerdrWorktree(pi: HerdrExtensionApi): void;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAA8B,MAAM,YAAY,CAAC;AAEhF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI,CAEnE"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { registerHerdrWorktreeTools } from "./tools.js";
2
+ export default function piHerdrWorktree(pi) {
3
+ registerHerdrWorktreeTools(pi);
4
+ }
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAEhF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAqB;IAC3D,0BAA0B,CAAC,EAAE,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,33 @@
1
+ export interface CommandResult {
2
+ readonly command: string;
3
+ readonly args: readonly string[];
4
+ readonly exitCode: number;
5
+ readonly stdout: string;
6
+ readonly stderr: string;
7
+ }
8
+ export interface RunCommandOptions {
9
+ readonly cwd: string;
10
+ readonly signal?: AbortSignal | undefined;
11
+ readonly maxOutputBytes?: number | undefined;
12
+ }
13
+ export type CommandRunner = (command: string, args: readonly string[], options: RunCommandOptions) => Promise<CommandResult>;
14
+ export declare class CommandInvocationError extends Error {
15
+ readonly command: string;
16
+ readonly code: string | undefined;
17
+ constructor(command: string, message: string, code: string | undefined, options?: ErrorOptions);
18
+ }
19
+ export declare class CommandCancelledError extends Error {
20
+ constructor(command: string);
21
+ }
22
+ /**
23
+ * Node kills the child once its output passes `maxBuffer`. That is a size limit, not a
24
+ * broken executable, so it gets its own type instead of looking like a failed spawn.
25
+ */
26
+ export declare class CommandOutputLimitError extends Error {
27
+ readonly command: string;
28
+ readonly maxOutputBytes: number;
29
+ constructor(command: string, maxOutputBytes: number, options?: ErrorOptions);
30
+ }
31
+ export declare const runCommand: CommandRunner;
32
+ export declare function runChecked(runner: CommandRunner, command: string, args: readonly string[], options: RunCommandOptions, failureContext: string): Promise<CommandResult>;
33
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../src/process.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9C;AAED,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,iBAAiB,KACvB,OAAO,CAAC,aAAa,CAAC,CAAC;AAY5B,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAElC,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,YAAY,EAK7F;CACF;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;;GAGG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,YAAY,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAO1E;CACF;AAED,eAAO,MAAM,UAAU,EAAE,aAmDxB,CAAC;AAEF,wBAAsB,UAAU,CAC9B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,iBAAiB,EAC1B,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,aAAa,CAAC,CAOxB"}