taskplane 0.25.2 → 0.25.4

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.
@@ -25,7 +25,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
25
25
  import { Type } from "@mariozechner/pi-ai";
26
26
  import { writeFileSync, readFileSync, existsSync, mkdirSync, renameSync, unlinkSync } from "fs";
27
27
  import { join, dirname } from "path";
28
- import { spawn as nodeSpawn } from "child_process";
28
+ import { spawn as nodeSpawn, spawnSync } from "child_process";
29
29
  import { randomBytes } from "crypto";
30
30
  import { buildExpansionRequestId, type SegmentExpansionRequest } from "./types.ts";
31
31
 
@@ -359,32 +359,63 @@ export default function (pi: ExtensionAPI) {
359
359
  /**
360
360
  * Resolve the Pi CLI entrypoint path (same logic as agent-host.ts).
361
361
  */
362
+ let _npmRootCache: string | null = null;
363
+ function getNpmGlobalRoot(): string {
364
+ if (_npmRootCache !== null) return _npmRootCache;
365
+ try {
366
+ const result = spawnSync("npm", ["root", "-g"], { encoding: "utf-8", timeout: 5000, shell: true });
367
+ _npmRootCache = result.stdout?.trim() || "";
368
+ } catch { _npmRootCache = ""; }
369
+ return _npmRootCache;
370
+ }
371
+
362
372
  function resolvePiCli(): string {
363
- const relPath = join("node_modules", "@mariozechner", "pi-coding-agent", "dist", "cli.js");
373
+ const relPath = join("@mariozechner", "pi-coding-agent", "dist", "cli.js");
364
374
  const candidates: string[] = [];
365
- if (process.env.APPDATA) candidates.push(join(process.env.APPDATA, "npm", relPath));
375
+
376
+ // Dynamic: npm root -g (covers nvm, Homebrew, volta, and all custom prefixes)
377
+ const npmRoot = getNpmGlobalRoot();
378
+ if (npmRoot) candidates.push(join(npmRoot, relPath));
379
+
380
+ // Well-known static paths
366
381
  const home = process.env.HOME || process.env.USERPROFILE || "";
382
+ if (process.env.APPDATA) candidates.push(join(process.env.APPDATA, "npm", "node_modules", relPath));
367
383
  if (home) {
368
- candidates.push(join(home, "AppData", "Roaming", "npm", relPath));
369
- candidates.push(join(home, ".npm-global", "lib", relPath));
384
+ candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", relPath));
385
+ candidates.push(join(home, ".npm-global", "lib", "node_modules", relPath));
370
386
  }
371
- candidates.push(join("/usr", "local", "lib", relPath));
387
+ candidates.push(join("/usr", "local", "lib", "node_modules", relPath));
388
+ candidates.push(join("/opt", "homebrew", "lib", "node_modules", relPath));
389
+
372
390
  for (const c of candidates) {
373
391
  if (existsSync(c)) return c;
374
392
  }
375
- throw new Error("Cannot find Pi CLI entrypoint");
393
+ throw new Error("Cannot find Pi CLI entrypoint. Run: npm root -g to verify your npm global path.");
376
394
  }
377
395
 
378
396
  /**
379
397
  * Load the reviewer system prompt from base template + local override.
380
398
  */
381
399
  function loadReviewerPrompt(): string {
382
- const basePaths = [
383
- process.env.APPDATA ? join(process.env.APPDATA, "npm", "node_modules", "taskplane", "templates", "agents", "task-reviewer.md") : "",
384
- (process.env.HOME || process.env.USERPROFILE || "") ? join(process.env.HOME || process.env.USERPROFILE || "", "AppData", "Roaming", "npm", "node_modules", "taskplane", "templates", "agents", "task-reviewer.md") : "",
385
- ].filter(Boolean);
400
+ const relPath = join("taskplane", "templates", "agents", "task-reviewer.md");
401
+ const candidates: string[] = [];
402
+
403
+ // Dynamic: npm root -g
404
+ const npmRoot = getNpmGlobalRoot();
405
+ if (npmRoot) candidates.push(join(npmRoot, relPath));
406
+
407
+ // Well-known static paths
408
+ const home = process.env.HOME || process.env.USERPROFILE || "";
409
+ if (process.env.APPDATA) candidates.push(join(process.env.APPDATA, "npm", "node_modules", relPath));
410
+ if (home) {
411
+ candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", relPath));
412
+ candidates.push(join(home, ".npm-global", "lib", "node_modules", relPath));
413
+ }
414
+ candidates.push(join("/usr", "local", "lib", "node_modules", relPath));
415
+ candidates.push(join("/opt", "homebrew", "lib", "node_modules", relPath));
416
+
386
417
  let basePrompt = "You are a code reviewer. Read the request and write your review to the specified output file.";
387
- for (const p of basePaths) {
418
+ for (const p of candidates) {
388
419
  try {
389
420
  if (!existsSync(p)) continue;
390
421
  const raw = readFileSync(p, "utf-8");
@@ -20,7 +20,7 @@
20
20
  * @since TP-104
21
21
  */
22
22
 
23
- import { spawn, type ChildProcess } from "child_process";
23
+ import { spawn, spawnSync, type ChildProcess } from "child_process";
24
24
  import {
25
25
  readFileSync, writeFileSync, appendFileSync, mkdirSync,
26
26
  existsSync, readdirSync, renameSync,
@@ -55,46 +55,54 @@ import { appendMailboxAuditEvent } from "./mailbox.ts";
55
55
  * with `shell: false`. This function resolves the underlying JS file.
56
56
  *
57
57
  * Resolution order:
58
- * 1. APPDATA/npm/node_modules/@mariozechner/pi-coding-agent/dist/cli.js
59
- * 2. HOME/.npm-global/lib/node_modules/...
60
- * 3. /usr/local/lib/node_modules/...
58
+ * 1. npm root -g (dynamic — covers nvm, Homebrew, volta, custom prefix)
59
+ * 2. APPDATA/npm/node_modules/... (Windows)
60
+ * 3. HOME/.npm-global/lib/node_modules/...
61
+ * 4. /usr/local/lib/node_modules/...
62
+ * 5. /opt/homebrew/lib/node_modules/... (macOS Homebrew)
61
63
  *
62
64
  * @returns Absolute path to the Pi CLI JS entrypoint
63
65
  * @throws Error if Pi CLI cannot be found
64
66
  *
65
67
  * @since TP-104
66
68
  */
69
+ let _npmGlobalRootCache: string | null = null;
70
+ function getNpmGlobalRoot(): string {
71
+ if (_npmGlobalRootCache !== null) return _npmGlobalRootCache;
72
+ try {
73
+ const result = spawnSync("npm", ["root", "-g"], { encoding: "utf-8", timeout: 5000, shell: true });
74
+ _npmGlobalRootCache = result.stdout?.trim() || "";
75
+ } catch {
76
+ _npmGlobalRootCache = "";
77
+ }
78
+ return _npmGlobalRootCache;
79
+ }
80
+
67
81
  export function resolvePiCliPath(): string {
68
- const relPath = join("node_modules", "@mariozechner", "pi-coding-agent", "dist", "cli.js");
82
+ const relPath = join("@mariozechner", "pi-coding-agent", "dist", "cli.js");
69
83
  const candidates: string[] = [];
70
84
 
71
- if (process.env.APPDATA) {
72
- candidates.push(join(process.env.APPDATA, "npm", relPath));
73
- }
85
+ // Dynamic first: covers nvm, Homebrew, volta, custom npm prefix
86
+ const npmRoot = getNpmGlobalRoot();
87
+ if (npmRoot) candidates.push(join(npmRoot, relPath));
88
+
89
+ // Well-known static fallbacks
74
90
  const home = process.env.HOME || process.env.USERPROFILE || "";
91
+ if (process.env.APPDATA) candidates.push(join(process.env.APPDATA, "npm", "node_modules", relPath));
75
92
  if (home) {
76
- candidates.push(join(home, "AppData", "Roaming", "npm", relPath));
77
- candidates.push(join(home, ".npm-global", "lib", relPath));
93
+ candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", relPath));
94
+ candidates.push(join(home, ".npm-global", "lib", "node_modules", relPath));
78
95
  }
79
- candidates.push(join("/usr", "local", "lib", relPath));
80
- candidates.push(join("/opt", "homebrew", "lib", relPath));
81
-
82
- // Dynamic: npm root -g
83
- try {
84
- const { spawnSync } = require("child_process");
85
- const result = spawnSync("npm", ["root", "-g"], { encoding: "utf-8", timeout: 5000, shell: true });
86
- if (result.stdout?.trim()) {
87
- candidates.push(join(result.stdout.trim(), "@mariozechner", "pi-coding-agent", "dist", "cli.js"));
88
- }
89
- } catch { /* ignore */ }
96
+ candidates.push(join("/usr", "local", "lib", "node_modules", relPath));
97
+ candidates.push(join("/opt", "homebrew", "lib", "node_modules", relPath));
90
98
 
91
99
  for (const candidate of candidates) {
92
100
  if (existsSync(candidate)) return candidate;
93
101
  }
94
102
 
95
103
  throw new Error(
96
- "Cannot find Pi CLI entrypoint (cli.js). Ensure pi is installed globally. " +
97
- `Searched: ${candidates.slice(0, 4).join(", ")}`,
104
+ "Cannot find Pi CLI entrypoint (cli.js). Ensure pi is installed globally via 'pi install'. " +
105
+ `npm root -g: ${npmRoot || "(not found)"}`,
98
106
  );
99
107
  }
100
108
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.25.2",
3
+ "version": "0.25.4",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",