taskplane 0.28.3 → 0.28.5
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 +21 -21
- package/README.md +215 -215
- package/bin/gitignore-patterns.mjs +79 -79
- package/bin/rpc-wrapper.mjs +1086 -1086
- package/bin/taskplane.mjs +3254 -3254
- package/dashboard/public/app.js +2573 -2573
- package/dashboard/public/index.html +139 -139
- package/dashboard/public/style.css +1882 -1882
- package/dashboard/public/taskplane-word-color.svg +18 -18
- package/dashboard/public/taskplane-word-white.svg +18 -18
- package/dashboard/server.cjs +1666 -1666
- package/extensions/reviewer-extension.ts +119 -119
- package/extensions/task-orchestrator.ts +28 -28
- package/extensions/taskplane/abort.ts +502 -502
- package/extensions/taskplane/agent-bridge-extension.ts +765 -765
- package/extensions/taskplane/agent-host.ts +833 -745
- package/extensions/taskplane/cleanup.ts +747 -747
- package/extensions/taskplane/config-loader.ts +1328 -1322
- package/extensions/taskplane/config-schema.ts +692 -682
- package/extensions/taskplane/config.ts +73 -73
- package/extensions/taskplane/context-window.ts +66 -66
- package/extensions/taskplane/diagnostic-reports.ts +463 -463
- package/extensions/taskplane/diagnostics.ts +385 -385
- package/extensions/taskplane/engine-worker-entry.mjs +34 -34
- package/extensions/taskplane/engine-worker.ts +381 -381
- package/extensions/taskplane/engine.ts +4539 -4527
- package/extensions/taskplane/execution.ts +2733 -2708
- package/extensions/taskplane/extension.ts +30 -9
- package/extensions/taskplane/formatting.ts +773 -773
- package/extensions/taskplane/git.ts +90 -90
- package/extensions/taskplane/index.ts +28 -28
- package/extensions/taskplane/lane-runner.ts +1383 -1360
- package/extensions/taskplane/mailbox.ts +689 -689
- package/extensions/taskplane/merge.ts +3135 -3135
- package/extensions/taskplane/messages.ts +985 -985
- package/extensions/taskplane/migrations.ts +278 -278
- package/extensions/taskplane/naming.ts +117 -117
- package/extensions/taskplane/path-resolver.ts +237 -237
- package/extensions/taskplane/persistence.ts +2087 -2087
- package/extensions/taskplane/process-registry.ts +416 -416
- package/extensions/taskplane/quality-gate.ts +1033 -1033
- package/extensions/taskplane/resume.ts +2879 -2878
- package/extensions/taskplane/sessions.ts +57 -57
- package/extensions/taskplane/settings-loader.ts +136 -136
- package/extensions/taskplane/settings-tui.ts +1867 -1865
- package/extensions/taskplane/sidecar-telemetry.ts +252 -252
- package/extensions/taskplane/supervisor-primer.md +1694 -1694
- package/extensions/taskplane/supervisor.ts +4341 -4341
- package/extensions/taskplane/task-executor-core.ts +550 -550
- package/extensions/taskplane/tmux-compat.ts +37 -37
- package/extensions/taskplane/types.ts +4297 -4278
- package/extensions/taskplane/verification.ts +542 -542
- package/extensions/taskplane/waves.ts +1548 -1548
- package/extensions/taskplane/workspace.ts +705 -705
- package/extensions/taskplane/worktree.ts +2604 -2505
- package/package.json +57 -57
- package/skills/create-taskplane-task/SKILL.md +465 -465
- package/skills/create-taskplane-task/references/prompt-template.md +285 -285
- package/templates/agents/local/supervisor.md +33 -33
- package/templates/agents/local/task-merger.md +27 -27
- package/templates/agents/local/task-reviewer.md +30 -30
- package/templates/agents/local/task-worker.md +34 -34
- package/templates/agents/supervisor-routing.md +92 -92
- package/templates/agents/supervisor.md +168 -168
- package/templates/agents/task-merger.md +214 -214
- package/templates/agents/task-reviewer.md +192 -192
- package/templates/agents/task-worker.md +429 -429
- package/templates/tasks/EXAMPLE-001-hello-world/PROMPT.md +98 -98
- package/templates/tasks/EXAMPLE-001-hello-world/STATUS.md +73 -73
- package/templates/tasks/EXAMPLE-002-parallel-smoke/PROMPT.md +97 -97
- package/templates/tasks/EXAMPLE-002-parallel-smoke/STATUS.md +73 -73
|
@@ -1,237 +1,237 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path Resolver — Consolidated npm global root detection and package/tool path resolution.
|
|
3
|
-
*
|
|
4
|
-
* This module is the single source of truth for resolving paths to globally-installed
|
|
5
|
-
* npm packages (taskplane and the pi coding agent CLI). It was created to eliminate
|
|
6
|
-
* three duplicate implementations that previously existed in execution.ts, agent-host.ts,
|
|
7
|
-
* and agent-bridge-extension.ts.
|
|
8
|
-
*
|
|
9
|
-
* ## Why this module exists
|
|
10
|
-
*
|
|
11
|
-
* macOS-specific bugs (#472, #474) were caused by hardcoded path lists that missed
|
|
12
|
-
* Homebrew (`/opt/homebrew`) and contained ESM-unsafe `require()` calls. Each fix had
|
|
13
|
-
* to be applied to multiple files, risking future drift. A single module eliminates that.
|
|
14
|
-
*
|
|
15
|
-
* ## Platform coverage
|
|
16
|
-
*
|
|
17
|
-
* - **Windows** — npm global root is typically `%APPDATA%\npm\node_modules` or a custom
|
|
18
|
-
* prefix. Static fallbacks cover both the APPDATA env var path and the HOME-relative
|
|
19
|
-
* equivalent (`AppData\Roaming\npm\node_modules`).
|
|
20
|
-
*
|
|
21
|
-
* - **macOS** — Multiple valid npm setups are covered:
|
|
22
|
-
* - System Node via Homebrew: `/opt/homebrew/lib/node_modules`
|
|
23
|
-
* - System Node (legacy): `/usr/local/lib/node_modules`
|
|
24
|
-
* - nvm, volta, or custom prefix: resolved dynamically via `npm root -g`
|
|
25
|
-
* - Custom global prefix: `~/.npm-global/lib/node_modules`
|
|
26
|
-
*
|
|
27
|
-
* - **Linux** — System Node (`/usr/local/lib/node_modules`), nvm, volta, and custom
|
|
28
|
-
* prefixes are all covered dynamically via `npm root -g`.
|
|
29
|
-
*
|
|
30
|
-
* ## Resolution strategy
|
|
31
|
-
*
|
|
32
|
-
* `npm root -g` is the **primary** resolution path because it covers every npm setup
|
|
33
|
-
* (nvm, Homebrew, volta, pnpm global, and any custom `--prefix`) with a single call.
|
|
34
|
-
* Static fallbacks exist only for environments where `npm` is not on PATH, which is
|
|
35
|
-
* uncommon but can happen in certain CI containers or restricted environments.
|
|
36
|
-
*
|
|
37
|
-
* The `npm root -g` result is module-level cached because:
|
|
38
|
-
* 1. It is called from multiple callsites per process.
|
|
39
|
-
* 2. The result never changes within a process lifetime.
|
|
40
|
-
* 3. Spawning a subprocess on every call would be expensive.
|
|
41
|
-
*
|
|
42
|
-
* @module taskplane/path-resolver
|
|
43
|
-
* @since TP-157
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
import { spawnSync } from "child_process";
|
|
47
|
-
import { existsSync } from "fs";
|
|
48
|
-
import { join, resolve } from "path";
|
|
49
|
-
|
|
50
|
-
// ── Module-level cache ──────────────────────────────────────────────
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Cached result of `npm root -g`.
|
|
54
|
-
* `null` = not yet resolved; `""` = resolution failed.
|
|
55
|
-
*/
|
|
56
|
-
let _npmGlobalRoot: string | null = null;
|
|
57
|
-
|
|
58
|
-
// ── Exported functions ──────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get the global npm root directory via `npm root -g`.
|
|
62
|
-
*
|
|
63
|
-
* The result is cached at module level for the process lifetime, so repeated
|
|
64
|
-
* calls are free after the first.
|
|
65
|
-
*
|
|
66
|
-
* @returns Absolute path to the npm global `node_modules` directory,
|
|
67
|
-
* or `""` if the call fails (npm not on PATH, subprocess error, etc.).
|
|
68
|
-
* Never throws.
|
|
69
|
-
*
|
|
70
|
-
* @platform Windows — `shell: true` is required because `npm` resolves to
|
|
71
|
-
* `npm.cmd`, a Windows batch script that cannot be spawned without a shell.
|
|
72
|
-
*/
|
|
73
|
-
export function getNpmGlobalRoot(): string {
|
|
74
|
-
if (_npmGlobalRoot !== null) return _npmGlobalRoot;
|
|
75
|
-
try {
|
|
76
|
-
const result = spawnSync("npm", ["root", "-g"], {
|
|
77
|
-
encoding: "utf-8",
|
|
78
|
-
timeout: 5000,
|
|
79
|
-
// shell: true is mandatory on Windows — npm resolves to npm.cmd
|
|
80
|
-
shell: true,
|
|
81
|
-
});
|
|
82
|
-
_npmGlobalRoot = result.stdout?.trim() || "";
|
|
83
|
-
} catch {
|
|
84
|
-
_npmGlobalRoot = "";
|
|
85
|
-
}
|
|
86
|
-
return _npmGlobalRoot;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Resolve the absolute path to the Pi coding agent CLI entrypoint (`cli.js`).
|
|
91
|
-
*
|
|
92
|
-
* The Pi CLI is installed as `@mariozechner/pi-coding-agent`. On Windows, invoking
|
|
93
|
-
* `pi` directly executes a `.CMD` shim that cannot be spawned with `shell: false`.
|
|
94
|
-
* This function locates the underlying `dist/cli.js` so callers can spawn it with
|
|
95
|
-
* `node` directly, without a shell intermediary.
|
|
96
|
-
*
|
|
97
|
-
* Resolution order:
|
|
98
|
-
* 1. `npm root -g` result (dynamic — covers all setups: nvm, Homebrew, volta, etc.)
|
|
99
|
-
* 2. `%APPDATA%\npm\node_modules\...` (Windows, APPDATA env var)
|
|
100
|
-
* 3. `%USERPROFILE%\AppData\Roaming\npm\node_modules\...` (Windows, HOME-relative)
|
|
101
|
-
* 4. `~/.npm-global/lib/node_modules/...` (macOS/Linux custom global prefix)
|
|
102
|
-
* 5. `/usr/local/lib/node_modules/...` (macOS system Node, Linux)
|
|
103
|
-
* 6. `/opt/homebrew/lib/node_modules/...` (macOS Homebrew)
|
|
104
|
-
*
|
|
105
|
-
* @returns Absolute path to `@mariozechner/pi-coding-agent/dist/cli.js`
|
|
106
|
-
* @throws {Error} If the CLI entrypoint cannot be found in any known location.
|
|
107
|
-
* The error message includes the `npm root -g` value for diagnosis.
|
|
108
|
-
*/
|
|
109
|
-
export function resolvePiCliPath(): string {
|
|
110
|
-
const relPath = join("@mariozechner", "pi-coding-agent", "dist", "cli.js");
|
|
111
|
-
const candidates: string[] = [];
|
|
112
|
-
|
|
113
|
-
// 1. Dynamic: npm root -g (covers nvm, Homebrew, volta, custom npm prefix, etc.)
|
|
114
|
-
const npmRoot = getNpmGlobalRoot();
|
|
115
|
-
if (npmRoot) candidates.push(join(npmRoot, relPath));
|
|
116
|
-
|
|
117
|
-
// 2-3. Static Windows fallbacks
|
|
118
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
119
|
-
if (process.env.APPDATA) {
|
|
120
|
-
candidates.push(join(process.env.APPDATA, "npm", "node_modules", relPath));
|
|
121
|
-
}
|
|
122
|
-
if (home) {
|
|
123
|
-
candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", relPath));
|
|
124
|
-
// 4. macOS/Linux custom global prefix
|
|
125
|
-
candidates.push(join(home, ".npm-global", "lib", "node_modules", relPath));
|
|
126
|
-
}
|
|
127
|
-
// 5. macOS system Node / Linux
|
|
128
|
-
candidates.push(join("/usr", "local", "lib", "node_modules", relPath));
|
|
129
|
-
// 6. macOS Homebrew
|
|
130
|
-
candidates.push(join("/opt", "homebrew", "lib", "node_modules", relPath));
|
|
131
|
-
|
|
132
|
-
for (const candidate of candidates) {
|
|
133
|
-
if (existsSync(candidate)) return candidate;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
throw new Error(
|
|
137
|
-
"Cannot find Pi CLI entrypoint (@mariozechner/pi-coding-agent/dist/cli.js). " +
|
|
138
|
-
"Ensure the pi coding agent is installed globally via 'npm install -g @mariozechner/pi-coding-agent'. " +
|
|
139
|
-
`npm root -g returned: ${npmRoot || "(empty — npm may not be on PATH)"}`,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Resolve the path to a file within the taskplane npm package.
|
|
145
|
-
*
|
|
146
|
-
* This handles both local development (running from the taskplane repo itself)
|
|
147
|
-
* and the installed-package case (taskplane installed globally via npm).
|
|
148
|
-
*
|
|
149
|
-
* Resolution order:
|
|
150
|
-
* 1. `join(repoRoot, relPath)` — local development (taskplane's own repo)
|
|
151
|
-
* 2. `npm root -g` result: `{npmGlobalRoot}/taskplane/{relPath}` (dynamic, all setups)
|
|
152
|
-
* 3. `{APPDATA}/npm/node_modules/taskplane/{relPath}` (Windows)
|
|
153
|
-
* 4. `{HOME}/AppData/Roaming/npm/node_modules/taskplane/{relPath}` (Windows alt)
|
|
154
|
-
* 5. `{HOME}/.npm-global/lib/node_modules/taskplane/{relPath}` (macOS/Linux custom prefix)
|
|
155
|
-
* 6. `/usr/local/lib/node_modules/taskplane/{relPath}` (macOS system Node, Linux)
|
|
156
|
-
* 7. `/opt/homebrew/lib/node_modules/taskplane/{relPath}` (macOS Homebrew)
|
|
157
|
-
* 8. Peer of pi's package (adjacent to `process.argv[1]`)
|
|
158
|
-
*
|
|
159
|
-
* @param repoRoot - Absolute path to the project root (used for local dev check)
|
|
160
|
-
* @param relPath - Relative path within the taskplane package, e.g.
|
|
161
|
-
* `"extensions/task-orchestrator.ts"` or `"templates/agents/task-worker.md"`
|
|
162
|
-
* @returns Absolute path to the resolved file. If not found in any location,
|
|
163
|
-
* returns the local path (`join(repoRoot, relPath)`) as a fallback — callers
|
|
164
|
-
* will fail at use time with a clear "file not found" error.
|
|
165
|
-
*/
|
|
166
|
-
export function resolveTaskplanePackageFile(repoRoot: string, relPath: string): string {
|
|
167
|
-
// 1. Local development — taskplane's own repo
|
|
168
|
-
const localPath = join(resolve(repoRoot), relPath);
|
|
169
|
-
if (existsSync(localPath)) return localPath;
|
|
170
|
-
|
|
171
|
-
const candidates: string[] = [];
|
|
172
|
-
|
|
173
|
-
// 2. Dynamic: npm root -g (covers ALL npm setups: nvm, Homebrew, volta, etc.)
|
|
174
|
-
const npmRoot = getNpmGlobalRoot();
|
|
175
|
-
if (npmRoot) {
|
|
176
|
-
candidates.push(join(npmRoot, "taskplane", relPath));
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// 3-7. Well-known static paths
|
|
180
|
-
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
181
|
-
if (process.env.APPDATA) {
|
|
182
|
-
candidates.push(join(process.env.APPDATA, "npm", "node_modules", "taskplane", relPath));
|
|
183
|
-
}
|
|
184
|
-
if (home) {
|
|
185
|
-
candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane", relPath));
|
|
186
|
-
candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane", relPath));
|
|
187
|
-
}
|
|
188
|
-
candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane", relPath));
|
|
189
|
-
candidates.push(join("/opt", "homebrew", "lib", "node_modules", "taskplane", relPath));
|
|
190
|
-
|
|
191
|
-
// 8. Peer of pi's package (look adjacent to pi's CLI entrypoint).
|
|
192
|
-
// pi is at: <npmRoot>/@mariozechner/pi-coding-agent/dist/cli.js
|
|
193
|
-
// so piPkgDir = <npmRoot>/@mariozechner/pi-coding-agent (resolve up 2 levels from cli.js)
|
|
194
|
-
// then go up TWO more levels to reach <npmRoot>, then into taskplane/
|
|
195
|
-
try {
|
|
196
|
-
const piPath = process.argv[1] || "";
|
|
197
|
-
const piPkgDir = resolve(piPath, "..", ".."); // <npmRoot>/@mariozechner/pi-coding-agent
|
|
198
|
-
const npmRootFromPi = resolve(piPkgDir, "..", ".."); // <npmRoot>
|
|
199
|
-
candidates.push(join(npmRootFromPi, "taskplane", relPath));
|
|
200
|
-
} catch { /* ignore — process.argv[1] may be undefined in test contexts */ }
|
|
201
|
-
|
|
202
|
-
for (const candidate of candidates) {
|
|
203
|
-
if (existsSync(candidate)) return candidate;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Fallback: return the local path. Callers will fail with a clear error at use time.
|
|
207
|
-
return localPath;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Resolve the path to a taskplane agent template file.
|
|
212
|
-
*
|
|
213
|
-
* Convenience wrapper around {@link resolveTaskplanePackageFile} for the
|
|
214
|
-
* common case of locating a file in `templates/agents/`.
|
|
215
|
-
*
|
|
216
|
-
* Used by `loadBaseAgentPrompt` (execution.ts) and `loadReviewerPrompt`
|
|
217
|
-
* (agent-bridge-extension.ts) to locate the base agent prompt templates
|
|
218
|
-
* that ship with the taskplane package.
|
|
219
|
-
*
|
|
220
|
-
* @param agentName - Agent template name without extension, e.g. `"task-worker"`,
|
|
221
|
-
* `"task-reviewer"`, `"task-merger"`
|
|
222
|
-
* @returns Absolute path to `templates/agents/{agentName}.md` within the
|
|
223
|
-
* resolved taskplane package root.
|
|
224
|
-
*
|
|
225
|
-
* @example
|
|
226
|
-
* ```ts
|
|
227
|
-
* const templatePath = resolveTaskplaneAgentTemplate("task-worker");
|
|
228
|
-
* // → "/usr/local/lib/node_modules/taskplane/templates/agents/task-worker.md"
|
|
229
|
-
* // (or local dev path, or any other resolved location)
|
|
230
|
-
* ```
|
|
231
|
-
*/
|
|
232
|
-
export function resolveTaskplaneAgentTemplate(agentName: string): string {
|
|
233
|
-
return resolveTaskplanePackageFile(
|
|
234
|
-
process.cwd(),
|
|
235
|
-
join("templates", "agents", `${agentName}.md`),
|
|
236
|
-
);
|
|
237
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Path Resolver — Consolidated npm global root detection and package/tool path resolution.
|
|
3
|
+
*
|
|
4
|
+
* This module is the single source of truth for resolving paths to globally-installed
|
|
5
|
+
* npm packages (taskplane and the pi coding agent CLI). It was created to eliminate
|
|
6
|
+
* three duplicate implementations that previously existed in execution.ts, agent-host.ts,
|
|
7
|
+
* and agent-bridge-extension.ts.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this module exists
|
|
10
|
+
*
|
|
11
|
+
* macOS-specific bugs (#472, #474) were caused by hardcoded path lists that missed
|
|
12
|
+
* Homebrew (`/opt/homebrew`) and contained ESM-unsafe `require()` calls. Each fix had
|
|
13
|
+
* to be applied to multiple files, risking future drift. A single module eliminates that.
|
|
14
|
+
*
|
|
15
|
+
* ## Platform coverage
|
|
16
|
+
*
|
|
17
|
+
* - **Windows** — npm global root is typically `%APPDATA%\npm\node_modules` or a custom
|
|
18
|
+
* prefix. Static fallbacks cover both the APPDATA env var path and the HOME-relative
|
|
19
|
+
* equivalent (`AppData\Roaming\npm\node_modules`).
|
|
20
|
+
*
|
|
21
|
+
* - **macOS** — Multiple valid npm setups are covered:
|
|
22
|
+
* - System Node via Homebrew: `/opt/homebrew/lib/node_modules`
|
|
23
|
+
* - System Node (legacy): `/usr/local/lib/node_modules`
|
|
24
|
+
* - nvm, volta, or custom prefix: resolved dynamically via `npm root -g`
|
|
25
|
+
* - Custom global prefix: `~/.npm-global/lib/node_modules`
|
|
26
|
+
*
|
|
27
|
+
* - **Linux** — System Node (`/usr/local/lib/node_modules`), nvm, volta, and custom
|
|
28
|
+
* prefixes are all covered dynamically via `npm root -g`.
|
|
29
|
+
*
|
|
30
|
+
* ## Resolution strategy
|
|
31
|
+
*
|
|
32
|
+
* `npm root -g` is the **primary** resolution path because it covers every npm setup
|
|
33
|
+
* (nvm, Homebrew, volta, pnpm global, and any custom `--prefix`) with a single call.
|
|
34
|
+
* Static fallbacks exist only for environments where `npm` is not on PATH, which is
|
|
35
|
+
* uncommon but can happen in certain CI containers or restricted environments.
|
|
36
|
+
*
|
|
37
|
+
* The `npm root -g` result is module-level cached because:
|
|
38
|
+
* 1. It is called from multiple callsites per process.
|
|
39
|
+
* 2. The result never changes within a process lifetime.
|
|
40
|
+
* 3. Spawning a subprocess on every call would be expensive.
|
|
41
|
+
*
|
|
42
|
+
* @module taskplane/path-resolver
|
|
43
|
+
* @since TP-157
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { spawnSync } from "child_process";
|
|
47
|
+
import { existsSync } from "fs";
|
|
48
|
+
import { join, resolve } from "path";
|
|
49
|
+
|
|
50
|
+
// ── Module-level cache ──────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Cached result of `npm root -g`.
|
|
54
|
+
* `null` = not yet resolved; `""` = resolution failed.
|
|
55
|
+
*/
|
|
56
|
+
let _npmGlobalRoot: string | null = null;
|
|
57
|
+
|
|
58
|
+
// ── Exported functions ──────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the global npm root directory via `npm root -g`.
|
|
62
|
+
*
|
|
63
|
+
* The result is cached at module level for the process lifetime, so repeated
|
|
64
|
+
* calls are free after the first.
|
|
65
|
+
*
|
|
66
|
+
* @returns Absolute path to the npm global `node_modules` directory,
|
|
67
|
+
* or `""` if the call fails (npm not on PATH, subprocess error, etc.).
|
|
68
|
+
* Never throws.
|
|
69
|
+
*
|
|
70
|
+
* @platform Windows — `shell: true` is required because `npm` resolves to
|
|
71
|
+
* `npm.cmd`, a Windows batch script that cannot be spawned without a shell.
|
|
72
|
+
*/
|
|
73
|
+
export function getNpmGlobalRoot(): string {
|
|
74
|
+
if (_npmGlobalRoot !== null) return _npmGlobalRoot;
|
|
75
|
+
try {
|
|
76
|
+
const result = spawnSync("npm", ["root", "-g"], {
|
|
77
|
+
encoding: "utf-8",
|
|
78
|
+
timeout: 5000,
|
|
79
|
+
// shell: true is mandatory on Windows — npm resolves to npm.cmd
|
|
80
|
+
shell: true,
|
|
81
|
+
});
|
|
82
|
+
_npmGlobalRoot = result.stdout?.trim() || "";
|
|
83
|
+
} catch {
|
|
84
|
+
_npmGlobalRoot = "";
|
|
85
|
+
}
|
|
86
|
+
return _npmGlobalRoot;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the absolute path to the Pi coding agent CLI entrypoint (`cli.js`).
|
|
91
|
+
*
|
|
92
|
+
* The Pi CLI is installed as `@mariozechner/pi-coding-agent`. On Windows, invoking
|
|
93
|
+
* `pi` directly executes a `.CMD` shim that cannot be spawned with `shell: false`.
|
|
94
|
+
* This function locates the underlying `dist/cli.js` so callers can spawn it with
|
|
95
|
+
* `node` directly, without a shell intermediary.
|
|
96
|
+
*
|
|
97
|
+
* Resolution order:
|
|
98
|
+
* 1. `npm root -g` result (dynamic — covers all setups: nvm, Homebrew, volta, etc.)
|
|
99
|
+
* 2. `%APPDATA%\npm\node_modules\...` (Windows, APPDATA env var)
|
|
100
|
+
* 3. `%USERPROFILE%\AppData\Roaming\npm\node_modules\...` (Windows, HOME-relative)
|
|
101
|
+
* 4. `~/.npm-global/lib/node_modules/...` (macOS/Linux custom global prefix)
|
|
102
|
+
* 5. `/usr/local/lib/node_modules/...` (macOS system Node, Linux)
|
|
103
|
+
* 6. `/opt/homebrew/lib/node_modules/...` (macOS Homebrew)
|
|
104
|
+
*
|
|
105
|
+
* @returns Absolute path to `@mariozechner/pi-coding-agent/dist/cli.js`
|
|
106
|
+
* @throws {Error} If the CLI entrypoint cannot be found in any known location.
|
|
107
|
+
* The error message includes the `npm root -g` value for diagnosis.
|
|
108
|
+
*/
|
|
109
|
+
export function resolvePiCliPath(): string {
|
|
110
|
+
const relPath = join("@mariozechner", "pi-coding-agent", "dist", "cli.js");
|
|
111
|
+
const candidates: string[] = [];
|
|
112
|
+
|
|
113
|
+
// 1. Dynamic: npm root -g (covers nvm, Homebrew, volta, custom npm prefix, etc.)
|
|
114
|
+
const npmRoot = getNpmGlobalRoot();
|
|
115
|
+
if (npmRoot) candidates.push(join(npmRoot, relPath));
|
|
116
|
+
|
|
117
|
+
// 2-3. Static Windows fallbacks
|
|
118
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
119
|
+
if (process.env.APPDATA) {
|
|
120
|
+
candidates.push(join(process.env.APPDATA, "npm", "node_modules", relPath));
|
|
121
|
+
}
|
|
122
|
+
if (home) {
|
|
123
|
+
candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", relPath));
|
|
124
|
+
// 4. macOS/Linux custom global prefix
|
|
125
|
+
candidates.push(join(home, ".npm-global", "lib", "node_modules", relPath));
|
|
126
|
+
}
|
|
127
|
+
// 5. macOS system Node / Linux
|
|
128
|
+
candidates.push(join("/usr", "local", "lib", "node_modules", relPath));
|
|
129
|
+
// 6. macOS Homebrew
|
|
130
|
+
candidates.push(join("/opt", "homebrew", "lib", "node_modules", relPath));
|
|
131
|
+
|
|
132
|
+
for (const candidate of candidates) {
|
|
133
|
+
if (existsSync(candidate)) return candidate;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
throw new Error(
|
|
137
|
+
"Cannot find Pi CLI entrypoint (@mariozechner/pi-coding-agent/dist/cli.js). " +
|
|
138
|
+
"Ensure the pi coding agent is installed globally via 'npm install -g @mariozechner/pi-coding-agent'. " +
|
|
139
|
+
`npm root -g returned: ${npmRoot || "(empty — npm may not be on PATH)"}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolve the path to a file within the taskplane npm package.
|
|
145
|
+
*
|
|
146
|
+
* This handles both local development (running from the taskplane repo itself)
|
|
147
|
+
* and the installed-package case (taskplane installed globally via npm).
|
|
148
|
+
*
|
|
149
|
+
* Resolution order:
|
|
150
|
+
* 1. `join(repoRoot, relPath)` — local development (taskplane's own repo)
|
|
151
|
+
* 2. `npm root -g` result: `{npmGlobalRoot}/taskplane/{relPath}` (dynamic, all setups)
|
|
152
|
+
* 3. `{APPDATA}/npm/node_modules/taskplane/{relPath}` (Windows)
|
|
153
|
+
* 4. `{HOME}/AppData/Roaming/npm/node_modules/taskplane/{relPath}` (Windows alt)
|
|
154
|
+
* 5. `{HOME}/.npm-global/lib/node_modules/taskplane/{relPath}` (macOS/Linux custom prefix)
|
|
155
|
+
* 6. `/usr/local/lib/node_modules/taskplane/{relPath}` (macOS system Node, Linux)
|
|
156
|
+
* 7. `/opt/homebrew/lib/node_modules/taskplane/{relPath}` (macOS Homebrew)
|
|
157
|
+
* 8. Peer of pi's package (adjacent to `process.argv[1]`)
|
|
158
|
+
*
|
|
159
|
+
* @param repoRoot - Absolute path to the project root (used for local dev check)
|
|
160
|
+
* @param relPath - Relative path within the taskplane package, e.g.
|
|
161
|
+
* `"extensions/task-orchestrator.ts"` or `"templates/agents/task-worker.md"`
|
|
162
|
+
* @returns Absolute path to the resolved file. If not found in any location,
|
|
163
|
+
* returns the local path (`join(repoRoot, relPath)`) as a fallback — callers
|
|
164
|
+
* will fail at use time with a clear "file not found" error.
|
|
165
|
+
*/
|
|
166
|
+
export function resolveTaskplanePackageFile(repoRoot: string, relPath: string): string {
|
|
167
|
+
// 1. Local development — taskplane's own repo
|
|
168
|
+
const localPath = join(resolve(repoRoot), relPath);
|
|
169
|
+
if (existsSync(localPath)) return localPath;
|
|
170
|
+
|
|
171
|
+
const candidates: string[] = [];
|
|
172
|
+
|
|
173
|
+
// 2. Dynamic: npm root -g (covers ALL npm setups: nvm, Homebrew, volta, etc.)
|
|
174
|
+
const npmRoot = getNpmGlobalRoot();
|
|
175
|
+
if (npmRoot) {
|
|
176
|
+
candidates.push(join(npmRoot, "taskplane", relPath));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 3-7. Well-known static paths
|
|
180
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
181
|
+
if (process.env.APPDATA) {
|
|
182
|
+
candidates.push(join(process.env.APPDATA, "npm", "node_modules", "taskplane", relPath));
|
|
183
|
+
}
|
|
184
|
+
if (home) {
|
|
185
|
+
candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane", relPath));
|
|
186
|
+
candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane", relPath));
|
|
187
|
+
}
|
|
188
|
+
candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane", relPath));
|
|
189
|
+
candidates.push(join("/opt", "homebrew", "lib", "node_modules", "taskplane", relPath));
|
|
190
|
+
|
|
191
|
+
// 8. Peer of pi's package (look adjacent to pi's CLI entrypoint).
|
|
192
|
+
// pi is at: <npmRoot>/@mariozechner/pi-coding-agent/dist/cli.js
|
|
193
|
+
// so piPkgDir = <npmRoot>/@mariozechner/pi-coding-agent (resolve up 2 levels from cli.js)
|
|
194
|
+
// then go up TWO more levels to reach <npmRoot>, then into taskplane/
|
|
195
|
+
try {
|
|
196
|
+
const piPath = process.argv[1] || "";
|
|
197
|
+
const piPkgDir = resolve(piPath, "..", ".."); // <npmRoot>/@mariozechner/pi-coding-agent
|
|
198
|
+
const npmRootFromPi = resolve(piPkgDir, "..", ".."); // <npmRoot>
|
|
199
|
+
candidates.push(join(npmRootFromPi, "taskplane", relPath));
|
|
200
|
+
} catch { /* ignore — process.argv[1] may be undefined in test contexts */ }
|
|
201
|
+
|
|
202
|
+
for (const candidate of candidates) {
|
|
203
|
+
if (existsSync(candidate)) return candidate;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Fallback: return the local path. Callers will fail with a clear error at use time.
|
|
207
|
+
return localPath;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Resolve the path to a taskplane agent template file.
|
|
212
|
+
*
|
|
213
|
+
* Convenience wrapper around {@link resolveTaskplanePackageFile} for the
|
|
214
|
+
* common case of locating a file in `templates/agents/`.
|
|
215
|
+
*
|
|
216
|
+
* Used by `loadBaseAgentPrompt` (execution.ts) and `loadReviewerPrompt`
|
|
217
|
+
* (agent-bridge-extension.ts) to locate the base agent prompt templates
|
|
218
|
+
* that ship with the taskplane package.
|
|
219
|
+
*
|
|
220
|
+
* @param agentName - Agent template name without extension, e.g. `"task-worker"`,
|
|
221
|
+
* `"task-reviewer"`, `"task-merger"`
|
|
222
|
+
* @returns Absolute path to `templates/agents/{agentName}.md` within the
|
|
223
|
+
* resolved taskplane package root.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* const templatePath = resolveTaskplaneAgentTemplate("task-worker");
|
|
228
|
+
* // → "/usr/local/lib/node_modules/taskplane/templates/agents/task-worker.md"
|
|
229
|
+
* // (or local dev path, or any other resolved location)
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
export function resolveTaskplaneAgentTemplate(agentName: string): string {
|
|
233
|
+
return resolveTaskplanePackageFile(
|
|
234
|
+
process.cwd(),
|
|
235
|
+
join("templates", "agents", `${agentName}.md`),
|
|
236
|
+
);
|
|
237
|
+
}
|