taskplane 0.9.3 → 0.10.1

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 CHANGED
@@ -19,10 +19,12 @@ The taskplane dashboard runs on a local port on your system and gives you elegan
19
19
  ### Key Features
20
20
 
21
21
  - **Task Orchestrator** — Parallel multi-task execution using git worktrees for full filesystem isolation. Dependency-aware wave scheduling. Automated merges into a dedicated orch branch — your working branch stays stable until you choose to integrate.
22
- - **Task Runner** — What the Orchestrator uses for autonomous single-task execution. Worker agents run in fresh-context loops with STATUS.md as persistent memory. Every checkbox gets a git checkpoint. Cross-model reviewer agents catch what the worker agents missed.
23
- - **Web Dashboard** — Live browser-based monitoring via `taskplane dashboard`. SSE streaming, lane/task progress, wave visualization, batch history.
22
+ - **Persistent Worker Context** — Workers handle all steps in a single context, auto-detecting the model's context window (1M for Claude 4.6 Opus, 200K for Bedrock). Only iterates on context overflow. Dramatic reduction in spawn count and token cost.
23
+ - **Worker-Driven Inline Reviews** — Workers invoke a `review_step` tool at step boundaries. Reviewer agents spawn in tmux sessions with full telemetry. REVISE feedback is addressed inline without losing context.
24
+ - **Supervisor Agent** — Conversational supervisor monitors batch progress, handles failures, and can invoke orchestrator commands autonomously (resume, integrate, pause, abort).
25
+ - **Web Dashboard** — Live browser-based monitoring via `taskplane dashboard`. SSE streaming, lane/task progress, reviewer activity, merge telemetry, batch history.
24
26
  - **Structured Tasks** — PROMPT.md defines the mission, steps, and constraints. STATUS.md tracks progress. Agents follow the plan, not vibes.
25
- - **Checkpoint Discipline** — Every completed checkbox item triggers a git commit. Work is never lost, even if a worker crashes mid-task.
27
+ - **Checkpoint Discipline** — Step boundary commits ensure work is never lost, even if a worker crashes mid-task.
26
28
  - **Cross-Model Review** — Reviewer agent uses a different model than the worker agent (highly recommended, not enforced). Independent quality gate before merge.
27
29
 
28
30
  ## Install
@@ -117,27 +119,17 @@ Inside the pi session:
117
119
 
118
120
  `/orch` with no arguments is the universal entry point — it detects your project state and activates the supervisor for guided interaction (onboarding, batch planning, health checks, or retrospective). The default scaffold includes two independent example tasks, so `/orch all` gives you an immediate orchestrator + dashboard experience.
119
121
 
120
- ### 4. Optional: run one task directly
122
+ ### 4. Run a single task with isolation
121
123
 
122
- `/task` is still useful for single-task execution and focused debugging:
123
-
124
- ```
125
- /task taskplane-tasks/EXAMPLE-001-hello-world/PROMPT.md
126
- /task-status
127
- ```
128
-
129
- Important distinction:
130
-
131
- - `/task` runs in your **current branch/worktree**.
132
- - `/orch` runs tasks in **isolated worktrees** on a dedicated orch branch — your working branch is never touched until you integrate.
133
-
134
- Because workers checkpoint with git commits, `/task` can capture unrelated local edits if you're changing files in parallel. For safer isolation (even with one task), prefer:
124
+ For a single task with full worktree isolation, dashboard, and reviews:
135
125
 
136
126
  ```text
137
127
  /orch taskplane-tasks/EXAMPLE-001-hello-world/PROMPT.md
138
128
  ```
139
129
 
140
- Orchestrator lanes execute tasks through task-runner under the hood, so `/task` and `/orch` share the same core task execution model.
130
+ This uses the same orchestrator infrastructure as a full batch isolated worktree, orch branch, supervisor, dashboard, inline reviews but for just one task.
131
+
132
+ > **Note:** The `/task` command still exists for direct single-task execution in the current branch, but `/orch` is recommended for all workflows. `/task` does not provide worktree isolation, dashboard, or inline reviews.
141
133
 
142
134
  ## Commands
143
135
 
@@ -200,9 +192,7 @@ Orchestrator lanes execute tasks through task-runner under the hood, so `/task`
200
192
  └─────────────┘
201
193
  ```
202
194
 
203
- **Single task** (`/task`): Worker iterates in fresh-context loops. STATUS.md is persistent memory. Each checkbox git checkpoint. Reviewer validates on completion.
204
-
205
- **Parallel batch** (`/orch`): Tasks are sorted into dependency waves. Each wave runs in parallel across lanes (git worktrees). Completed lanes merge into a dedicated orch branch. When the batch completes, use `/orch-integrate` to bring the results into your working branch (or configure auto-integration).
195
+ **How it works:** Tasks are sorted into dependency waves. Each wave runs in parallel across lanes (git worktrees). Workers handle all steps in a single context, calling `review_step` at step boundaries for inline reviews. Completed lanes merge into a dedicated orch branch. A supervisor agent monitors progress and can autonomously resume, integrate, or abort. When the batch completes, use `/orch-integrate` to bring the results into your working branch (or configure auto-integration).
206
196
 
207
197
  ## Documentation
208
198
 
@@ -78,15 +78,17 @@ function tokenSummaryFromLaneState(ls) {
78
78
  /** Build compact telemetry badge HTML for retry/compaction indicators.
79
79
  * Only shows badges when telemetry data has meaningful values.
80
80
  * @param {object|null} tel - Telemetry data for a lane (from currentData.telemetry[prefix])
81
+ * @param {boolean} [suppressRetry=false] - When true, hide the retrying badge
82
+ * (used when reviewer is active — long tool calls trigger false retry signals)
81
83
  * @returns {string} HTML string with badges, or "" if nothing to show
82
84
  */
83
- function telemetryBadgesHtml(tel) {
85
+ function telemetryBadgesHtml(tel, suppressRetry) {
84
86
  if (!tel) return "";
85
87
  let badges = "";
86
- if (tel.retryActive) {
88
+ if (tel.retryActive && !suppressRetry) {
87
89
  const err = tel.lastRetryError ? ` — ${tel.lastRetryError}` : "";
88
90
  badges += `<span class="telem-badge telem-retry-active" title="Retry in progress${escapeHtml(err)}">🔄 retrying</span>`;
89
- } else if (tel.retries > 0) {
91
+ } else if (tel.retries > 0 && !suppressRetry) {
90
92
  badges += `<span class="telem-badge telem-retry" title="${tel.retries} auto-retry event(s)">🔄 ${tel.retries}</span>`;
91
93
  }
92
94
  if (tel.compactions > 0) {
@@ -529,7 +531,7 @@ function renderLanesTasks(batch, tmuxSessions) {
529
531
 
530
532
  // Worker stats from lane state sidecar + telemetry badges
531
533
  let workerHtml = "";
532
- const telemBadges = task.status !== "pending" ? telemetryBadgesHtml(tel) : "";
534
+ const telemBadges = task.status !== "pending" ? telemetryBadgesHtml(tel, reviewerActive) : "";
533
535
  // Reviewer sub-row should only appear under the task currently being reviewed,
534
536
  // not all tasks in the lane. The lane-state sidecar is per-lane (shared by all
535
537
  // tasks in the lane), so check that the sidecar's current taskId matches this task.
@@ -12,43 +12,81 @@ import type { AllocatedLane, AllocatedTask, DependencyGraph, LaneExecutionResult
12
12
  import { allocateLanes } from "./waves.ts";
13
13
  import { runGit } from "./git.ts";
14
14
 
15
- // ── Task Runner Extension Path Resolution ────────────────────────────
15
+ // ── Taskplane Package File Resolution ────────────────────────────────
16
16
 
17
17
  /**
18
- * Find the task-runner extension path for lane sessions.
18
+ * Cached result of `npm root -g` to avoid repeated child process spawns.
19
+ * null = not yet resolved, "" = resolution failed.
20
+ */
21
+ let _npmGlobalRoot: string | null = null;
22
+
23
+ /**
24
+ * Get the global npm root directory via `npm root -g`.
25
+ * Result is cached for the process lifetime.
26
+ */
27
+ function getNpmGlobalRoot(): string {
28
+ if (_npmGlobalRoot !== null) return _npmGlobalRoot;
29
+ try {
30
+ const result = spawnSync("npm", ["root", "-g"], {
31
+ encoding: "utf-8",
32
+ timeout: 5000,
33
+ shell: true,
34
+ });
35
+ _npmGlobalRoot = result.stdout?.trim() || "";
36
+ } catch {
37
+ _npmGlobalRoot = "";
38
+ }
39
+ return _npmGlobalRoot;
40
+ }
41
+
42
+ /**
43
+ * Resolve a file path within the taskplane package.
19
44
  *
20
45
  * Resolution order:
21
- * 1. Local project: {repoRoot}/extensions/task-runner.ts (for taskplane dev)
22
- * 2. Global npm (Windows): {APPDATA}/npm/node_modules/taskplane/extensions/task-runner.ts
23
- * 3. Global npm (Unix): /usr/local/lib/node_modules/taskplane/extensions/task-runner.ts
24
- * 4. npm peer: resolve from pi's location
25
- *
26
- * @throws ExecutionError if task-runner.ts cannot be found anywhere
46
+ * 1. Local project: {repoRoot}/{relPath} (for taskplane development)
47
+ * 2. `npm root -g` based: {npmGlobalRoot}/taskplane/{relPath}
48
+ * (covers Homebrew, nvm, volta, pnpm, and any custom npm prefix)
49
+ * 3. Well-known global npm paths (Windows/macOS/Linux):
50
+ * - {APPDATA}/npm/node_modules/taskplane/{relPath}
51
+ * - {HOME}/.npm-global/lib/node_modules/taskplane/{relPath}
52
+ * - /usr/local/lib/node_modules/taskplane/{relPath}
53
+ * - /opt/homebrew/lib/node_modules/taskplane/{relPath}
54
+ * 4. Peer of pi's package: resolve from pi's binary location
55
+ *
56
+ * @param repoRoot - Absolute path to the project root
57
+ * @param relPath - Relative path within the taskplane package (e.g., "bin/rpc-wrapper.mjs")
58
+ * @returns Absolute path to the resolved file
27
59
  */
28
- function resolveTaskRunnerExtensionPath(repoRoot: string): string {
29
- const extFile = join("extensions", "task-runner.ts");
30
-
60
+ function resolveTaskplanePackageFile(repoRoot: string, relPath: string): string {
31
61
  // 1. Local project (taskplane development)
32
- const localPath = join(resolve(repoRoot), extFile);
62
+ const localPath = join(resolve(repoRoot), relPath);
33
63
  if (existsSync(localPath)) return localPath;
34
64
 
35
- // 2. Global npm install paths
36
- const home = process.env.HOME || process.env.USERPROFILE || "";
37
65
  const candidates: string[] = [];
66
+
67
+ // 2. Dynamic: `npm root -g` (covers ALL npm setups: nvm, Homebrew, volta, etc.)
68
+ const npmRoot = getNpmGlobalRoot();
69
+ if (npmRoot) {
70
+ candidates.push(join(npmRoot, "taskplane", relPath));
71
+ }
72
+
73
+ // 3. Well-known static paths
74
+ const home = process.env.HOME || process.env.USERPROFILE || "";
38
75
  if (process.env.APPDATA) {
39
- candidates.push(join(process.env.APPDATA, "npm", "node_modules", "taskplane", extFile));
76
+ candidates.push(join(process.env.APPDATA, "npm", "node_modules", "taskplane", relPath));
40
77
  }
41
78
  if (home) {
42
- candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane", extFile));
43
- candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane", extFile));
79
+ candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane", relPath));
80
+ candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane", relPath));
44
81
  }
45
- candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane", extFile));
82
+ candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane", relPath));
83
+ candidates.push(join("/opt", "homebrew", "lib", "node_modules", "taskplane", relPath));
46
84
 
47
- // 3. Peer of pi's package
85
+ // 4. Peer of pi's package
48
86
  try {
49
87
  const piPath = process.argv[1] || "";
50
88
  const piPkgDir = resolve(piPath, "..", "..");
51
- candidates.push(join(piPkgDir, "..", "taskplane", extFile));
89
+ candidates.push(join(piPkgDir, "..", "taskplane", relPath));
52
90
  } catch { /* ignore */ }
53
91
 
54
92
  for (const candidate of candidates) {
@@ -59,51 +97,24 @@ function resolveTaskRunnerExtensionPath(repoRoot: string): string {
59
97
  return localPath;
60
98
  }
61
99
 
100
+ // ── Task Runner Extension Path Resolution ────────────────────────────
101
+
102
+ /**
103
+ * Find the task-runner extension path for lane sessions.
104
+ * @see resolveTaskplanePackageFile for resolution order
105
+ */
106
+ function resolveTaskRunnerExtensionPath(repoRoot: string): string {
107
+ return resolveTaskplanePackageFile(repoRoot, join("extensions", "task-runner.ts"));
108
+ }
109
+
62
110
  // ── RPC Wrapper Path Resolution ──────────────────────────────────────
63
111
 
64
112
  /**
65
113
  * Find the rpc-wrapper.mjs path for lane sessions.
66
- *
67
- * Resolution order mirrors resolveTaskRunnerExtensionPath:
68
- * 1. Local project: {repoRoot}/bin/rpc-wrapper.mjs (for taskplane dev)
69
- * 2. Global npm (Windows): {APPDATA}/npm/node_modules/taskplane/bin/rpc-wrapper.mjs
70
- * 3. Global npm (Unix): /usr/local/lib/node_modules/taskplane/bin/rpc-wrapper.mjs
71
- * 4. npm peer: resolve from pi's location
72
- *
73
- * @throws ExecutionError if rpc-wrapper.mjs cannot be found anywhere
114
+ * @see resolveTaskplanePackageFile for resolution order
74
115
  */
75
116
  export function resolveRpcWrapperPath(repoRoot: string): string {
76
- const wrapperFile = join("bin", "rpc-wrapper.mjs");
77
-
78
- // 1. Local project (taskplane development)
79
- const localPath = join(resolve(repoRoot), wrapperFile);
80
- if (existsSync(localPath)) return localPath;
81
-
82
- // 2. Global npm install paths
83
- const home = process.env.HOME || process.env.USERPROFILE || "";
84
- const candidates: string[] = [];
85
- if (process.env.APPDATA) {
86
- candidates.push(join(process.env.APPDATA, "npm", "node_modules", "taskplane", wrapperFile));
87
- }
88
- if (home) {
89
- candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane", wrapperFile));
90
- candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane", wrapperFile));
91
- }
92
- candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane", wrapperFile));
93
-
94
- // 3. Peer of pi's package
95
- try {
96
- const piPath = process.argv[1] || "";
97
- const piPkgDir = resolve(piPath, "..", "..");
98
- candidates.push(join(piPkgDir, "..", "taskplane", wrapperFile));
99
- } catch { /* ignore */ }
100
-
101
- for (const candidate of candidates) {
102
- if (existsSync(candidate)) return candidate;
103
- }
104
-
105
- // Fallback: return the local path (will fail at spawn time with a clear error)
106
- return localPath;
117
+ return resolveTaskplanePackageFile(repoRoot, join("bin", "rpc-wrapper.mjs"));
107
118
  }
108
119
 
109
120
  // ── Telemetry Helpers ────────────────────────────────────────────────