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.
Files changed (71) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +215 -215
  3. package/bin/gitignore-patterns.mjs +79 -79
  4. package/bin/rpc-wrapper.mjs +1086 -1086
  5. package/bin/taskplane.mjs +3254 -3254
  6. package/dashboard/public/app.js +2573 -2573
  7. package/dashboard/public/index.html +139 -139
  8. package/dashboard/public/style.css +1882 -1882
  9. package/dashboard/public/taskplane-word-color.svg +18 -18
  10. package/dashboard/public/taskplane-word-white.svg +18 -18
  11. package/dashboard/server.cjs +1666 -1666
  12. package/extensions/reviewer-extension.ts +119 -119
  13. package/extensions/task-orchestrator.ts +28 -28
  14. package/extensions/taskplane/abort.ts +502 -502
  15. package/extensions/taskplane/agent-bridge-extension.ts +765 -765
  16. package/extensions/taskplane/agent-host.ts +833 -745
  17. package/extensions/taskplane/cleanup.ts +747 -747
  18. package/extensions/taskplane/config-loader.ts +1328 -1322
  19. package/extensions/taskplane/config-schema.ts +692 -682
  20. package/extensions/taskplane/config.ts +73 -73
  21. package/extensions/taskplane/context-window.ts +66 -66
  22. package/extensions/taskplane/diagnostic-reports.ts +463 -463
  23. package/extensions/taskplane/diagnostics.ts +385 -385
  24. package/extensions/taskplane/engine-worker-entry.mjs +34 -34
  25. package/extensions/taskplane/engine-worker.ts +381 -381
  26. package/extensions/taskplane/engine.ts +4539 -4527
  27. package/extensions/taskplane/execution.ts +2733 -2708
  28. package/extensions/taskplane/extension.ts +30 -9
  29. package/extensions/taskplane/formatting.ts +773 -773
  30. package/extensions/taskplane/git.ts +90 -90
  31. package/extensions/taskplane/index.ts +28 -28
  32. package/extensions/taskplane/lane-runner.ts +1383 -1360
  33. package/extensions/taskplane/mailbox.ts +689 -689
  34. package/extensions/taskplane/merge.ts +3135 -3135
  35. package/extensions/taskplane/messages.ts +985 -985
  36. package/extensions/taskplane/migrations.ts +278 -278
  37. package/extensions/taskplane/naming.ts +117 -117
  38. package/extensions/taskplane/path-resolver.ts +237 -237
  39. package/extensions/taskplane/persistence.ts +2087 -2087
  40. package/extensions/taskplane/process-registry.ts +416 -416
  41. package/extensions/taskplane/quality-gate.ts +1033 -1033
  42. package/extensions/taskplane/resume.ts +2879 -2878
  43. package/extensions/taskplane/sessions.ts +57 -57
  44. package/extensions/taskplane/settings-loader.ts +136 -136
  45. package/extensions/taskplane/settings-tui.ts +1867 -1865
  46. package/extensions/taskplane/sidecar-telemetry.ts +252 -252
  47. package/extensions/taskplane/supervisor-primer.md +1694 -1694
  48. package/extensions/taskplane/supervisor.ts +4341 -4341
  49. package/extensions/taskplane/task-executor-core.ts +550 -550
  50. package/extensions/taskplane/tmux-compat.ts +37 -37
  51. package/extensions/taskplane/types.ts +4297 -4278
  52. package/extensions/taskplane/verification.ts +542 -542
  53. package/extensions/taskplane/waves.ts +1548 -1548
  54. package/extensions/taskplane/workspace.ts +705 -705
  55. package/extensions/taskplane/worktree.ts +2604 -2505
  56. package/package.json +57 -57
  57. package/skills/create-taskplane-task/SKILL.md +465 -465
  58. package/skills/create-taskplane-task/references/prompt-template.md +285 -285
  59. package/templates/agents/local/supervisor.md +33 -33
  60. package/templates/agents/local/task-merger.md +27 -27
  61. package/templates/agents/local/task-reviewer.md +30 -30
  62. package/templates/agents/local/task-worker.md +34 -34
  63. package/templates/agents/supervisor-routing.md +92 -92
  64. package/templates/agents/supervisor.md +168 -168
  65. package/templates/agents/task-merger.md +214 -214
  66. package/templates/agents/task-reviewer.md +192 -192
  67. package/templates/agents/task-worker.md +429 -429
  68. package/templates/tasks/EXAMPLE-001-hello-world/PROMPT.md +98 -98
  69. package/templates/tasks/EXAMPLE-001-hello-world/STATUS.md +73 -73
  70. package/templates/tasks/EXAMPLE-002-parallel-smoke/PROMPT.md +97 -97
  71. 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
+ }