opencode-herdr-orchestration 0.1.1 → 0.1.3
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/README.md +52 -0
- package/bin/orchestration.js +57 -9
- package/package.json +5 -2
- package/src/agents.js +6 -0
- package/src/installer.js +207 -0
- package/src/prompts.js +4 -0
package/README.md
CHANGED
|
@@ -8,6 +8,41 @@ Requires Node.js 22.22.2 or newer when running the package CLI or tests.
|
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
|
+
Install or update the plugin with the cross-platform npm CLI:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx -y opencode-herdr-orchestration@latest install
|
|
15
|
+
npx -y opencode-herdr-orchestration@latest update
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The installer:
|
|
19
|
+
|
|
20
|
+
- locates the global OpenCode config directory;
|
|
21
|
+
- installs an exact package version there;
|
|
22
|
+
- preserves existing JSONC comments, trailing commas, plugins, and tuple options;
|
|
23
|
+
- adds the stable file URL required by current OpenCode npm plugin loading;
|
|
24
|
+
- creates timestamped backups of changed config and npm manifest files;
|
|
25
|
+
- validates `shepherd-build` through a short-lived OpenCode debug process;
|
|
26
|
+
- never restarts or signals a running OpenCode process.
|
|
27
|
+
|
|
28
|
+
The shared Git push policy remains opt-in:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx -y opencode-herdr-orchestration@latest install --with-hooks
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Inspect or remove the installation:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx -y opencode-herdr-orchestration@latest status
|
|
38
|
+
npx -y opencode-herdr-orchestration@latest uninstall
|
|
39
|
+
npx -y opencode-herdr-orchestration@latest uninstall --with-hooks
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
After installation or update, quit and restart OpenCode intentionally when ready. Existing processes keep their already-loaded configuration.
|
|
43
|
+
|
|
44
|
+
## Manual Installation
|
|
45
|
+
|
|
11
46
|
Add the published package to the global OpenCode configuration at `~/.config/opencode/opencode.json` or `~/.config/opencode/opencode.jsonc`:
|
|
12
47
|
|
|
13
48
|
```jsonc
|
|
@@ -19,6 +54,23 @@ Add the published package to the global OpenCode configuration at `~/.config/ope
|
|
|
19
54
|
|
|
20
55
|
If `plugin` already contains entries, append `"opencode-herdr-orchestration"` instead of replacing them. OpenCode installs npm plugins automatically when it starts.
|
|
21
56
|
|
|
57
|
+
Install the package into that config directory so the stable file URL can resolve it:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd ~/.config/opencode
|
|
61
|
+
npm install --save-exact opencode-herdr-orchestration@latest
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Current OpenCode releases may record but not execute npm plugins referenced only by package name. If that occurs, use the installed entry path in the plugin tuple instead:
|
|
65
|
+
|
|
66
|
+
```jsonc
|
|
67
|
+
{
|
|
68
|
+
"plugin": [
|
|
69
|
+
"file:///ABSOLUTE/PATH/TO/.config/opencode/node_modules/opencode-herdr-orchestration/src/plugin.js"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
22
74
|
Optionally install the shared Git push policy for all current and future repositories:
|
|
23
75
|
|
|
24
76
|
```bash
|
package/bin/orchestration.js
CHANGED
|
@@ -5,6 +5,18 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
|
+
import {
|
|
9
|
+
configDirectory,
|
|
10
|
+
installPackage,
|
|
11
|
+
latestVersion,
|
|
12
|
+
PACKAGE_NAME,
|
|
13
|
+
packageVersion,
|
|
14
|
+
restoreBackup,
|
|
15
|
+
status as installationStatus,
|
|
16
|
+
uninstallPackage,
|
|
17
|
+
validateOpenCode,
|
|
18
|
+
writePluginConfig,
|
|
19
|
+
} from "../src/installer.js";
|
|
8
20
|
|
|
9
21
|
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
10
22
|
const hooksPath = join(homedir(), ".config", "opencode-herdr-orchestration", "hooks");
|
|
@@ -24,7 +36,7 @@ function currentHooksPath() {
|
|
|
24
36
|
}
|
|
25
37
|
}
|
|
26
38
|
|
|
27
|
-
function
|
|
39
|
+
function installHooks() {
|
|
28
40
|
const existing = currentHooksPath();
|
|
29
41
|
const force = flags.includes("--force");
|
|
30
42
|
if (existing && existing !== hooksPath && !force) {
|
|
@@ -37,7 +49,7 @@ function install() {
|
|
|
37
49
|
process.stdout.write(`Installed pre-push hook and set global core.hooksPath=${hooksPath}\n`);
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
function
|
|
52
|
+
function uninstallHooks() {
|
|
41
53
|
if (currentHooksPath() === hooksPath) {
|
|
42
54
|
git(["config", "--global", "--unset", "core.hooksPath"]);
|
|
43
55
|
rmSync(hooksPath, { recursive: true, force: true });
|
|
@@ -47,21 +59,57 @@ function uninstall() {
|
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
function
|
|
51
|
-
|
|
62
|
+
function installOrUpdate(useLatest) {
|
|
63
|
+
const configDir = configDirectory();
|
|
64
|
+
const version = useLatest ? latestVersion() : packageVersion(packageRoot);
|
|
65
|
+
const packageBackups = installPackage(configDir, version);
|
|
66
|
+
const result = writePluginConfig(configDir);
|
|
67
|
+
try {
|
|
68
|
+
validateOpenCode(configDir);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
restoreBackup(result.file, result.backup, result.existed);
|
|
71
|
+
throw new Error(`OpenCode validation failed; restored the previous config. ${error.message}`);
|
|
72
|
+
}
|
|
73
|
+
if (flags.includes("--with-hooks")) installHooks();
|
|
74
|
+
process.stdout.write(`Configured ${PACKAGE_NAME}@${version} in ${result.file}\n`);
|
|
75
|
+
if (result.backup) process.stdout.write(`Backup: ${result.backup}\n`);
|
|
76
|
+
for (const backup of packageBackups) process.stdout.write(`Backup: ${backup}\n`);
|
|
77
|
+
process.stdout.write("Restart OpenCode intentionally to load the new configuration.\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function uninstallOrchestration() {
|
|
81
|
+
const configDir = configDirectory();
|
|
82
|
+
const result = writePluginConfig(configDir, true);
|
|
83
|
+
const packageBackups = uninstallPackage(configDir);
|
|
84
|
+
if (flags.includes("--with-hooks")) uninstallHooks();
|
|
85
|
+
process.stdout.write(`Removed orchestration plugin configuration from ${result.file}\n`);
|
|
86
|
+
if (result.backup) process.stdout.write(`Backup: ${result.backup}\n`);
|
|
87
|
+
for (const backup of packageBackups) process.stdout.write(`Backup: ${backup}\n`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function fullStatus() {
|
|
91
|
+
const result = { ...installationStatus(configDirectory(), packageRoot), hooks: JSON.parse(captureHookStatus()) };
|
|
92
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function captureHookStatus() {
|
|
96
|
+
return JSON.stringify({
|
|
52
97
|
configuredHooksPath: currentHooksPath() || null,
|
|
53
98
|
expectedHooksPath: hooksPath,
|
|
54
99
|
hookInstalled: existsSync(installedHook),
|
|
55
100
|
active: currentHooksPath() === hooksPath && existsSync(installedHook),
|
|
56
|
-
}
|
|
101
|
+
});
|
|
57
102
|
}
|
|
58
103
|
|
|
59
104
|
try {
|
|
60
|
-
if (command === "install
|
|
61
|
-
else if (command === "
|
|
62
|
-
else if (command === "
|
|
105
|
+
if (command === "install") installOrUpdate(false);
|
|
106
|
+
else if (command === "update") installOrUpdate(true);
|
|
107
|
+
else if (command === "uninstall") uninstallOrchestration();
|
|
108
|
+
else if (command === "install-hooks") installHooks();
|
|
109
|
+
else if (command === "uninstall-hooks") uninstallHooks();
|
|
110
|
+
else if (command === "status") fullStatus();
|
|
63
111
|
else {
|
|
64
|
-
process.stderr.write("Usage: opencode-herdr-orchestration <install-hooks [--force]
|
|
112
|
+
process.stderr.write("Usage: opencode-herdr-orchestration <install|update|status|uninstall|install-hooks|uninstall-hooks> [--with-hooks] [--force]\n");
|
|
65
113
|
process.exitCode = 2;
|
|
66
114
|
}
|
|
67
115
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-herdr-orchestration",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Capability-separated Herdr orchestration agents for OpenCode",
|
|
5
5
|
"author": "CodingJinxx",
|
|
6
6
|
"repository": {
|
|
@@ -45,5 +45,8 @@
|
|
|
45
45
|
"orchestration",
|
|
46
46
|
"agents"
|
|
47
47
|
],
|
|
48
|
-
"license": "MIT"
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"jsonc-parser": "3.3.1"
|
|
51
|
+
}
|
|
49
52
|
}
|
package/src/agents.js
CHANGED
|
@@ -52,6 +52,12 @@ const herdrInspection = {
|
|
|
52
52
|
"herdr pane layout*": "allow",
|
|
53
53
|
"herdr pane split*": "allow",
|
|
54
54
|
"herdr pane read*": "allow",
|
|
55
|
+
"herdr worktree": "allow",
|
|
56
|
+
"herdr worktree list*": "allow",
|
|
57
|
+
"herdr worktree create *": "allow",
|
|
58
|
+
"herdr worktree open *": "allow",
|
|
59
|
+
"herdr worktree remove --workspace *": "allow",
|
|
60
|
+
"herdr worktree remove * --force*": "deny",
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
const markdownOnly = {
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser";
|
|
7
|
+
|
|
8
|
+
export const PACKAGE_NAME = "opencode-herdr-orchestration";
|
|
9
|
+
const NPM_COMMAND = process.platform === "win32" ? process.execPath : "npm";
|
|
10
|
+
const NPM_PREFIX = process.platform === "win32" ? [resolveNpmCli()] : [];
|
|
11
|
+
const OPENCODE_COMMAND = process.platform === "win32" ? "opencode.exe" : "opencode";
|
|
12
|
+
export const AGENT_NAMES = [
|
|
13
|
+
"shepherd-plan",
|
|
14
|
+
"shepherd-build",
|
|
15
|
+
"sheep-plan",
|
|
16
|
+
"sheep-build",
|
|
17
|
+
"shearer-review-low",
|
|
18
|
+
"shearer-review-medium",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function resolveWindowsCommand(name) {
|
|
22
|
+
try {
|
|
23
|
+
return execFileSync("where.exe", [name], { encoding: "utf8", windowsHide: true })
|
|
24
|
+
.split(/\r?\n/)
|
|
25
|
+
.find(Boolean)
|
|
26
|
+
.trim();
|
|
27
|
+
} catch {
|
|
28
|
+
return name;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveNpmCli() {
|
|
33
|
+
const npmShim = resolveWindowsCommand("npm.cmd");
|
|
34
|
+
return join(dirname(npmShim), "node_modules", "npm", "bin", "npm-cli.js");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function configDirectory(env = process.env) {
|
|
38
|
+
if (env.OPENCODE_CONFIG_DIR) return resolve(env.OPENCODE_CONFIG_DIR);
|
|
39
|
+
const home = env.HOME || env.USERPROFILE || homedir();
|
|
40
|
+
return join(home, ".config", "opencode");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function packageEntry(configDir) {
|
|
44
|
+
return pathToFileURL(join(configDir, "node_modules", PACKAGE_NAME, "src", "plugin.js")).href;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isOrchestrationPlugin(entry) {
|
|
48
|
+
const spec = Array.isArray(entry) ? entry[0] : entry;
|
|
49
|
+
return (
|
|
50
|
+
typeof spec === "string" &&
|
|
51
|
+
(spec === PACKAGE_NAME || spec.startsWith(`${PACKAGE_NAME}@`) || spec.includes(`/node_modules/${PACKAGE_NAME}/`))
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function updatePluginConfig(text, entry, remove = false) {
|
|
56
|
+
const errors = [];
|
|
57
|
+
const config = parse(text || "{}", errors, { allowTrailingComma: true, disallowComments: false });
|
|
58
|
+
if (errors.length) {
|
|
59
|
+
const first = errors[0];
|
|
60
|
+
throw new Error(`Invalid OpenCode JSONC at offset ${first.offset}: ${printParseErrorCode(first.error)}.`);
|
|
61
|
+
}
|
|
62
|
+
const formattingOptions = { insertSpaces: true, tabSize: 2, eol: text.includes("\r\n") ? "\r\n" : "\n" };
|
|
63
|
+
const plugins = Array.isArray(config?.plugin) ? config.plugin : [];
|
|
64
|
+
const matches = plugins.map((candidate, index) => isOrchestrationPlugin(candidate) ? index : -1).filter((index) => index >= 0);
|
|
65
|
+
const existing = matches.length ? plugins[matches[0]] : undefined;
|
|
66
|
+
const source = text || "{}";
|
|
67
|
+
|
|
68
|
+
if (matches.length === 1 && !remove) {
|
|
69
|
+
const replacement = Array.isArray(existing) ? [entry, existing[1] ?? {}] : entry;
|
|
70
|
+
return applyEdits(source, modify(source, ["plugin", matches[0]], replacement, { formattingOptions }));
|
|
71
|
+
}
|
|
72
|
+
if (matches.length && (remove || matches.length > 1)) {
|
|
73
|
+
const remaining = plugins.filter((candidate) => !isOrchestrationPlugin(candidate));
|
|
74
|
+
if (!remove) remaining.push(Array.isArray(existing) ? [entry, existing[1] ?? {}] : entry);
|
|
75
|
+
return applyEdits(source, modify(source, ["plugin"], remaining, { formattingOptions }));
|
|
76
|
+
}
|
|
77
|
+
if (!remove) {
|
|
78
|
+
const path = Array.isArray(config?.plugin) ? ["plugin", -1] : ["plugin"];
|
|
79
|
+
const value = Array.isArray(config?.plugin) ? entry : [entry];
|
|
80
|
+
return applyEdits(source, modify(source, path, value, { formattingOptions }));
|
|
81
|
+
}
|
|
82
|
+
return source;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function findConfigFile(configDir) {
|
|
86
|
+
for (const name of ["opencode.jsonc", "opencode.json"]) {
|
|
87
|
+
const candidate = join(configDir, name);
|
|
88
|
+
if (existsSync(candidate)) return candidate;
|
|
89
|
+
}
|
|
90
|
+
return join(configDir, "opencode.jsonc");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function backupFile(file, now = new Date()) {
|
|
94
|
+
if (!existsSync(file)) return null;
|
|
95
|
+
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
96
|
+
const backup = `${file}.backup-${stamp}`;
|
|
97
|
+
copyFileSync(file, backup);
|
|
98
|
+
return backup;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function run(command, args, options = {}) {
|
|
102
|
+
return execFileSync(command, args, {
|
|
103
|
+
encoding: "utf8",
|
|
104
|
+
windowsHide: true,
|
|
105
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
106
|
+
...options,
|
|
107
|
+
}).trim();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function installedVersion(configDir) {
|
|
111
|
+
const manifest = join(configDir, "node_modules", PACKAGE_NAME, "package.json");
|
|
112
|
+
if (!existsSync(manifest)) return null;
|
|
113
|
+
return JSON.parse(readFileSync(manifest, "utf8")).version;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function packageVersion(packageRoot) {
|
|
117
|
+
return JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")).version;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function latestVersion() {
|
|
121
|
+
return JSON.parse(run(NPM_COMMAND, [...NPM_PREFIX, "view", PACKAGE_NAME, "version", "--json", "--prefer-online"]));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function installPackage(configDir, version) {
|
|
125
|
+
mkdirSync(configDir, { recursive: true });
|
|
126
|
+
const backups = ["package.json", "package-lock.json"]
|
|
127
|
+
.map((name) => backupFile(join(configDir, name)))
|
|
128
|
+
.filter(Boolean);
|
|
129
|
+
run(NPM_COMMAND, [...NPM_PREFIX, "install", "--save-exact", `${PACKAGE_NAME}@${version}`], { cwd: configDir });
|
|
130
|
+
return backups;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function uninstallPackage(configDir) {
|
|
134
|
+
const backups = ["package.json", "package-lock.json"]
|
|
135
|
+
.map((name) => backupFile(join(configDir, name)))
|
|
136
|
+
.filter(Boolean);
|
|
137
|
+
try {
|
|
138
|
+
run(NPM_COMMAND, [...NPM_PREFIX, "uninstall", PACKAGE_NAME], { cwd: configDir });
|
|
139
|
+
} catch {}
|
|
140
|
+
return backups;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function writePluginConfig(configDir, remove = false) {
|
|
144
|
+
mkdirSync(configDir, { recursive: true });
|
|
145
|
+
const file = findConfigFile(configDir);
|
|
146
|
+
const existed = existsSync(file);
|
|
147
|
+
const previous = existed ? readFileSync(file, "utf8") : '{\n "$schema": "https://opencode.ai/config.json"\n}\n';
|
|
148
|
+
const next = updatePluginConfig(previous, packageEntry(configDir), remove);
|
|
149
|
+
if (next === previous) return { file, backup: null, changed: false, existed };
|
|
150
|
+
const backup = backupFile(file);
|
|
151
|
+
writeFileSync(file, next, "utf8");
|
|
152
|
+
return { file, backup, changed: true, existed };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function restoreBackup(file, backup, existed = true) {
|
|
156
|
+
if (backup && existsSync(backup)) copyFileSync(backup, file);
|
|
157
|
+
else if (!existed) rmSync(file, { force: true });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function validateOpenCode(configDir) {
|
|
161
|
+
const output = run(OPENCODE_COMMAND, ["debug", "agent", "shepherd-build"], {
|
|
162
|
+
cwd: configDir,
|
|
163
|
+
env: { ...process.env, OPENCODE_DISABLE_PROJECT_CONFIG: "1" },
|
|
164
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
165
|
+
});
|
|
166
|
+
const parsed = JSON.parse(output);
|
|
167
|
+
if (parsed.name !== "shepherd-build") throw new Error("OpenCode did not resolve shepherd-build after installation.");
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function status(configDir, packageRoot) {
|
|
172
|
+
const file = findConfigFile(configDir);
|
|
173
|
+
let configured = false;
|
|
174
|
+
if (existsSync(file)) {
|
|
175
|
+
const config = parse(readFileSync(file, "utf8"), [], { allowTrailingComma: true, disallowComments: false });
|
|
176
|
+
configured = Array.isArray(config?.plugin) && config.plugin.some(isOrchestrationPlugin);
|
|
177
|
+
}
|
|
178
|
+
const installed = installedVersion(configDir);
|
|
179
|
+
let detectedAgents = [];
|
|
180
|
+
try {
|
|
181
|
+
const output = run(OPENCODE_COMMAND, ["agent", "list"], {
|
|
182
|
+
cwd: configDir,
|
|
183
|
+
env: { ...process.env, OPENCODE_DISABLE_PROJECT_CONFIG: "1" },
|
|
184
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
185
|
+
});
|
|
186
|
+
detectedAgents = AGENT_NAMES.filter((name) => output.includes(`${name} (primary)`));
|
|
187
|
+
} catch {}
|
|
188
|
+
let latest = null;
|
|
189
|
+
let latestVersionError = null;
|
|
190
|
+
try {
|
|
191
|
+
latest = latestVersion();
|
|
192
|
+
} catch (error) {
|
|
193
|
+
latestVersionError = String(error?.stderr ?? error?.message ?? error).replace(/\s+/g, " ").trim().slice(0, 500);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
package: PACKAGE_NAME,
|
|
197
|
+
cliVersion: packageVersion(packageRoot),
|
|
198
|
+
installedVersion: installed,
|
|
199
|
+
latestVersion: latest,
|
|
200
|
+
latestVersionError,
|
|
201
|
+
updateAvailable: Boolean(installed && latest && installed !== latest),
|
|
202
|
+
configFile: file,
|
|
203
|
+
pluginConfigured: configured,
|
|
204
|
+
detectedAgents,
|
|
205
|
+
agentsReady: detectedAgents.length === AGENT_NAMES.length,
|
|
206
|
+
};
|
|
207
|
+
}
|
package/src/prompts.js
CHANGED
|
@@ -13,6 +13,8 @@ Never pass another agent, model, --auto, or extra OpenCode argument. Use send-ke
|
|
|
13
13
|
|
|
14
14
|
Worker names must be unique and satisfy Herdr's naming rules. Assign bounded work with herdr agent prompt <name> "..." --wait --timeout <milliseconds>. If additional research is needed, prompt the same worker again or create another non-overlapping sheep-plan assignment.
|
|
15
15
|
|
|
16
|
+
When isolated planning needs a worktree, use Herdr's installed worktree commands. A worktree created from another worktree is a peer that shares the same Git common repository, not its child. Inspect existing worktrees first, use an explicit non-protected branch and base commit, and place the checkout outside the current worktree rather than nesting it. Record the worker, branch, path, and base commit. Remove only worktrees you created, only after their planning artifacts are preserved and the worktree is clean; never force removal.
|
|
17
|
+
|
|
16
18
|
After a worker settles, use herdr_agent_response as the authoritative result channel. Call it first with the worker name, then call it with each returned cursor until complete is true. Do not summarize, decide, or act on the worker 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 the completed worker response. If retrieval says the worker is not settled, wait and retry. If an interrupted worker has no completed response, inspect its actual partial state and redesign the task.
|
|
17
19
|
|
|
18
20
|
If a worker is blocked, inspect it with herdr agent get and herdr agent read; do not answer approvals or questions without applying the user's safety constraints. Treat unknown as inconclusive, not complete. Synthesize worker findings instead of forwarding raw reports. Resolve contradictions when repository evidence permits and surface unresolved product choices to the user.
|
|
@@ -45,6 +47,8 @@ Delegate using structured contracts containing, where relevant: task_id, plan_id
|
|
|
45
47
|
|
|
46
48
|
Parallelize only tasks that will not conflict. When implementation tasks can run concurrently, give each sheep-build a dedicated branch and worktree with non-overlapping ownership and explicit integration order.
|
|
47
49
|
|
|
50
|
+
Use Herdr's installed worktree commands for worker isolation. Worktrees created while you are already in a worktree are peers sharing the same Git common repository. Inspect the existing worktree list before creation, create each worker branch from the approved base commit, and never nest a worker checkout inside the current worktree. Record worker name, branch, path, base commit, and owned scope before delegation. Never reuse a branch checked out elsewhere. Remove only worktrees you created, only after their commits are integrated or otherwise preserved and the worktree is clean; never force removal.
|
|
51
|
+
|
|
48
52
|
Spawn only these configured workers with no extra OpenCode arguments:
|
|
49
53
|
|
|
50
54
|
herdr agent start <name> --kind opencode --pane <pane-id> -- --agent sheep-plan
|