taskplane 0.24.30 → 0.25.0

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
@@ -1,35 +1,47 @@
1
1
  # Taskplane
2
2
 
3
- Multi-agent AI orchestration for [pi](https://github.com/badlogic/pi-mono) — parallel task execution with checkpoint discipline, fresh-context worker loops, cross-model reviews, and automated merges.
3
+ Multi-agent AI orchestration for coding with [pi](https://github.com/badlogic/pi-mono) — parallel task execution, mono- and poly-repo support, fresh-context worker loops, cross-model reviews, automated merges and a killer dashboard!
4
4
 
5
- > **Status:** Experimental / Early — APIs and config formats may change between releases.
5
+ > **Status:** Initial release.
6
6
 
7
7
  ## What It Does
8
8
 
9
- ### STEP 1: Create the tasks
10
- Taskplane turns your coding project into an AI-managed task orchestration system. You simply ask your agent to create tasks using the built-in "create-taskplane-tasks" skill. This skill provides an opinionated task definition template designed to drive successful coding outcomes. Tasks define both the prompt.md and the status.md files that together act as the persistent memory store that allows AI coding agents to survive context resets and succeed with very long running tasks that would typically exhaust an agent's context window.
9
+ Taskplane turns ideas into high-quality code using a proven process of:
11
10
 
12
- ### STEP 2: Run batches of tasks
13
- The system works out the dependancy map for the entire batch of tasks then orchestrates them in waves, with appropriate parallelization and serialization.
11
+ have an idea >> create a spec >> create tasks >> orchestrate tasks >> evaluate the outcome
14
12
 
15
- The taskplane dashboard runs on a local port on your system and gives you elegant visibility into everything that's going on (a stark improvement over TUI-based dashboards).
13
+ ### Taskplane has:
14
+ - A skill for creating tasks that the Taskplane orchestrator can run
15
+ - PROMPT.md/STATUS.md task definition for persistent memory store
16
+ - Support for both monorepo and polyrepo projects
17
+ - Complete parallelized worktree isolation with dependency graphing and segment-level repo isolation
18
+ - 4 agent types: supervisor, worker, reviewer, and merger
19
+ - A deterministic orchestration engine to drive repeatable positive agent outcomes at scale
20
+ - A simple file-based mail system so agents can communicate with each other
21
+ - A killer locally-run web-based dashboard so you can see everything that's going on
16
22
 
17
23
  <img src="docs/images/orchrun-wave2of4-2lanes-withstatus.png" alt="image of taskplane dashboard" width="50%">
18
24
 
25
+ ### STEP 1: Create the tasks
26
+ Taskplane turns your coding project into an AI-managed task orchestration system. You simply ask your agent to create tasks using the built-in "create-taskplane-tasks" skill. This skill provides an opinionated task definition template designed to drive successful coding outcomes. Tasks define both the prompt.md and the status.md files that together act as the persistent memory store that allows AI coding agents to survive context resets and succeed with very long running tasks that would typically exhaust an agent's context window.
27
+
28
+ ### STEP 2: Run batches of tasks
29
+ Taskplane works out the dependency map for an entire batch of tasks then orchestrates them in waves, lanes, and tasks with appropriate parallelization and serialization. Taskplane can do this for both monorepo and polyrepo projects. For polyrepo projects, Taskplane additionally subdivides tasks into repo-aligned segments and uses a segmentation dependency map (DAG) to manage proper repo/worktree isolation and allow for dynamic segment expansion so worker agents can ask the supervisor agent to add additional segments to the dependency map in real time if required.
30
+
19
31
  ### Key Features
20
32
 
21
33
  - **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
- - **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.
34
+ - **Persistent Worker Context** — Workers handle all steps in a single context, auto-detecting the model's context window. Only iterates on context overflow. Dramatic reduction in spawn count and token cost.
35
+ - **Worker-Driven Inline Reviews** — Workers invoke a `review_step` tool at step boundaries. Reviewer agents spawn with full telemetry. REVISE feedback is addressed inline without losing context.
24
36
  - **Supervisor Agent** — Conversational supervisor monitors batch progress, handles failures, and can invoke orchestrator commands autonomously (resume, integrate, pause, abort).
25
37
  - **Web Dashboard** — Live browser-based monitoring via `taskplane dashboard`. SSE streaming, lane/task progress, reviewer activity, merge telemetry, batch history.
26
38
  - **Structured Tasks** — PROMPT.md defines the mission, steps, and constraints. STATUS.md tracks progress. Agents follow the plan, not vibes.
27
39
  - **Checkpoint Discipline** — Step boundary commits ensure work is never lost, even if a worker crashes mid-task.
28
40
  - **Cross-Model Review** — Reviewer agent uses a different model than the worker agent (highly recommended, not enforced). Independent quality gate before merge.
29
41
 
30
- ## Install
42
+ ## Installation
31
43
 
32
- Taskplane is a [pi package](https://github.com/badlogic/pi-mono). You need [Node.js](https://nodejs.org/) 22 and [pi](https://github.com/badlogic/pi-mono) installed first.
44
+ Taskplane is a pi package. You need Node.js 22+, pi and Git installed first.
33
45
 
34
46
  ### Prerequisites
35
47
 
@@ -38,51 +50,31 @@ Taskplane is a [pi package](https://github.com/badlogic/pi-mono). You need [Node
38
50
  | [Node.js](https://nodejs.org/) ≥ 22 | Yes | Runtime |
39
51
  | [pi](https://github.com/badlogic/pi-mono) | Yes | Agent framework |
40
52
  | [Git](https://git-scm.com/) | Yes | Version control, worktrees |
41
- | **tmux** | **Strongly recommended** | Required for `/orch` parallel execution |
42
53
 
43
- **tmux** is needed for the orchestrator to spawn parallel worker sessions. Without it, `/orch` will not work. On Windows, Taskplane can install it for you:
44
-
45
- ```bash
46
- taskplane install-tmux
47
- ```
48
-
49
- On macOS: `brew install tmux` · On Linux: `sudo apt install tmux` (or your distro's package manager)
50
-
51
- ### Option A: Global Install (all projects)
54
+ ### Option A: Global Install (all projects - recommended)
52
55
 
53
56
  ```bash
54
57
  pi install npm:taskplane
55
58
  ```
56
59
 
57
- ### Option B: Project-Local Install (recommended for teams)
60
+ ### Option B: Single Project-Local Install
58
61
 
59
62
  ```bash
60
63
  cd my-project
61
64
  pi install -l npm:taskplane
62
65
  ```
63
66
 
64
- Then scaffold your project:
65
-
66
- ```bash
67
- taskplane init
68
- ```
69
-
70
- Verify the installation:
71
-
72
- ```bash
73
- taskplane doctor
74
- ```
75
-
76
67
  ## Quickstart
77
68
 
78
- ### 1. Initialize a project
69
+ ### 1. Initialize a project (scaffolds settings)
79
70
 
80
71
  ```bash
81
72
  cd my-project
82
- taskplane init --preset full
73
+ taskplane init
83
74
  ```
75
+ You'll answer a few questions. You can usually just accept the defaults.
84
76
 
85
- This creates config files in `.pi/`, agent prompts, two example tasks, and adds `.gitignore` entries for runtime artifacts. On first install, init bootstraps global preferences at `~/.pi/agent/taskplane/preferences.json` with thinking defaults set to `high` for worker/reviewer/merger. Interactive init then prompts for worker/reviewer/merger model + thinking defaults (`inherit`, `off`, `minimal`, `low`, `medium`, `high`, `xhigh`). If 2+ providers are available from `pi --list-models`, init recommends cross-provider reviewer/merger selections. Init auto-detects whether you're in a single repo or a multi-repo workspace. See the [install tutorial](docs/tutorials/install.md) for workspace mode and other scenarios.
77
+ This creates config files in `.pi/`, agent prompts, two example tasks, and adds `.gitignore` entries for runtime artifacts. On first install, init bootstraps global preferences at `~/.pi/agent/taskplane/preferences.json` with thinking defaults set to `high` for worker & reviewer, and off for merger. Interactive init then prompts for worker/reviewer/merger model + thinking defaults (`inherit`, `off`, `minimal`, `low`, `medium`, `high`, `xhigh`). If 2+ providers are available from `pi --list-models`, init recommends cross-provider reviewer/merger selections. Init auto-detects whether you're in a single repo or a multi-repo workspace. See the [install tutorial](docs/tutorials/install.md) for workspace mode and other scenarios.
86
78
 
87
79
  Want to reuse model/thinking picks across projects? Run `taskplane config --save-as-defaults` in an initialized project.
88
80
 
@@ -94,7 +86,15 @@ taskplane init --preset full --tasks-root docs/task-management
94
86
 
95
87
  When `--tasks-root` is provided, example task packets are skipped by default. Add `--include-examples` if you explicitly want examples in that folder.
96
88
 
97
- ### 2. Launch the dashboard (recommended)
89
+ ### 2. Check your install with taskplane doctor
90
+
91
+ Verify the installation and scaffolding. You should have all green checkboxes if everything was successful:
92
+
93
+ ```bash
94
+ taskplane doctor
95
+ ```
96
+
97
+ ### 3. Launch the dashboard (recommended)
98
98
 
99
99
  In a separate terminal:
100
100
 
@@ -104,7 +104,7 @@ taskplane dashboard
104
104
 
105
105
  Opens a live web dashboard at `http://localhost:8099` with real-time batch monitoring.
106
106
 
107
- ### 3. Run your first orchestration
107
+ ### 4. Run your first orchestration
108
108
 
109
109
  ```bash
110
110
  pi
@@ -121,7 +121,7 @@ Inside the pi session:
121
121
 
122
122
  `/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.
123
123
 
124
- ### 4. Run a single task with isolation
124
+ ### 5. Run a single task with isolation
125
125
 
126
126
  For a single task with full worktree isolation, dashboard, and reviews:
127
127
 
@@ -131,18 +131,12 @@ For a single task with full worktree isolation, dashboard, and reviews:
131
131
 
132
132
  This uses the same orchestrator infrastructure as a full batch — isolated worktree, orch branch, supervisor, dashboard, inline reviews — but for just one task.
133
133
 
134
- > **Deprecated:** The `/task` command is deprecated and will be removed in a future major version. It does not provide worktree isolation, dashboard, or inline reviews. Use `/orch` for all workflows — including single-task execution.
135
-
136
134
  ## Commands
137
135
 
138
136
  ### Pi Session Commands
139
137
 
140
138
  | Command | Description |
141
139
  |---------|-------------|
142
- | `/task <path/to/PROMPT.md>` | ⚠️ **Deprecated.** Execute one task in the current branch/worktree. Use `/orch` instead. |
143
- | `/task-status` | ⚠️ **Deprecated.** Show current task progress. Use `/orch-status` or dashboard. |
144
- | `/task-pause` | ⚠️ **Deprecated.** Pause after current worker iteration finishes. Use `/orch-pause`. |
145
- | `/task-resume` | ⚠️ **Deprecated.** Resume a paused task. Use `/orch-resume`. |
146
140
  | `/orch [<areas\|paths\|all>]` | No args: detect state & guide (onboarding, batch planning, etc.); with args: execute tasks via isolated worktrees |
147
141
  | `/orch-plan <areas\|paths\|all>` | Preview execution plan without running |
148
142
  | `/orch-status` | Show batch progress |
@@ -176,8 +170,7 @@ This uses the same orchestrator infrastructure as a full batch — isolated work
176
170
  │ │ │
177
171
  ┌────▼────┐ ┌──▼─────┐ ┌──▼─────┐
178
172
  │ Lane 1 │ │ Lane 2 │ │ Lane 3 │ ← Git worktrees
179
- /task │ │ /task │ │ /task │ (isolated)
180
- │ Worker │ │ Worker │ │ Worker │
173
+ Worker │ │ Worker │ │ Worker │ (isolated)
181
174
  │ Review │ │ Review │ │ Review │
182
175
  └────┬────┘ └──┬─────┘ └──┬─────┘
183
176
  │ │ │
package/bin/taskplane.mjs CHANGED
@@ -316,93 +316,6 @@ function detectStack(projectRoot) {
316
316
 
317
317
  // ─── YAML Generation ────────────────────────────────────────────────────────
318
318
 
319
- function generateTaskRunnerYaml(vars) {
320
- return `# ═══════════════════════════════════════════════════════════════════════
321
- # Task Runner Configuration — ${vars.project_name}
322
- # ═══════════════════════════════════════════════════════════════════════
323
- #
324
- # This file configures the /task command (task-runner extension).
325
- # Edit freely — this file is owned by you, not the package.
326
-
327
- # ── Task Areas ────────────────────────────────────────────────────────
328
- # Define where tasks live. Each area has a folder path, ID prefix, and
329
- # a CONTEXT.md file that provides domain context to agents.
330
-
331
- task_areas:
332
- ${vars.default_area}:
333
- path: "${vars.tasks_root}"
334
- prefix: "${vars.default_prefix}"
335
- context: "${vars.tasks_root}/CONTEXT.md"
336
-
337
- # ── Reference Docs ────────────────────────────────────────────────────
338
- # Docs that tasks can reference in their "Context to Read First" section.
339
- # Add your project's architecture docs, API specs, etc.
340
-
341
- reference_docs: {}
342
-
343
- # ── Standards ─────────────────────────────────────────────────────────
344
- # Coding standards and rules. Agents follow these during implementation.
345
-
346
- standards: {}
347
-
348
- # ── Testing ───────────────────────────────────────────────────────────
349
- # Commands that agents run to verify their work.
350
-
351
- testing:
352
- commands:${vars.test_cmd ? `\n unit: "${vars.test_cmd}"` : ""}${vars.build_cmd ? `\n build: "${vars.build_cmd}"` : ""}
353
- `;
354
- }
355
-
356
- function generateOrchestratorYaml(vars) {
357
- return `# ═══════════════════════════════════════════════════════════════════════
358
- # Parallel Task Orchestrator Configuration — ${vars.project_name}
359
- # ═══════════════════════════════════════════════════════════════════════
360
- #
361
- # This file configures the /orch commands (task-orchestrator extension).
362
- # Edit freely — this file is owned by you, not the package.
363
-
364
- orchestrator:
365
- max_lanes: ${vars.max_lanes}
366
- worktree_location: "subdirectory"
367
- worktree_prefix: "${vars.worktree_prefix}"
368
- batch_id_format: "timestamp"
369
- spawn_mode: "${vars.spawn_mode}"
370
- session_prefix: "${vars.session_prefix}"
371
-
372
- dependencies:
373
- source: "prompt"
374
- cache: true
375
-
376
- assignment:
377
- strategy: "affinity-first"
378
- size_weights:
379
- S: 1
380
- M: 2
381
- L: 4
382
-
383
- pre_warm:
384
- auto_detect: false
385
- commands: {}
386
- always: []
387
-
388
- merge:
389
- model: ""
390
- tools: "read,write,edit,bash,grep,find,ls"
391
- verify: []
392
- order: "fewest-files-first"
393
-
394
- failure:
395
- on_task_failure: "skip-dependents"
396
- on_merge_failure: "pause"
397
- stall_timeout: 30
398
- max_worker_minutes: 30
399
- abort_grace_period: 60
400
-
401
- monitoring:
402
- poll_interval: 5
403
- `;
404
- }
405
-
406
319
  function buildTestingCommands(vars) {
407
320
  const commands = {};
408
321
  if (vars.test_cmd) commands.unit = vars.test_cmd;
@@ -964,6 +877,27 @@ async function autoCommitTaskFiles(projectRoot, tasksRoot) {
964
877
  }
965
878
 
966
879
  function discoverTaskAreaMetadata(projectRoot, configRoot = projectRoot, configPrefix = ".pi") {
880
+ // Prefer taskplane-config.json; fall back to task-runner.yaml for legacy projects
881
+ const jsonPath = path.join(configRoot, configPrefix, "taskplane-config.json");
882
+ if (fs.existsSync(jsonPath)) {
883
+ try {
884
+ const config = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
885
+ const areas = config?.taskRunner?.taskAreas;
886
+ if (areas && typeof areas === "object" && !Array.isArray(areas)) {
887
+ const paths = new Set();
888
+ const contexts = new Set();
889
+ const areaRepoIds = {};
890
+ for (const [areaName, area] of Object.entries(areas)) {
891
+ if (!area || typeof area !== "object") continue;
892
+ if (typeof area.path === "string" && area.path) paths.add(area.path);
893
+ if (typeof area.context === "string" && area.context) contexts.add(area.context);
894
+ if (typeof area.repoId === "string" && area.repoId) areaRepoIds[areaName] = area.repoId;
895
+ }
896
+ return { paths: [...paths], contexts: [...contexts], areaRepoIds };
897
+ }
898
+ } catch { /* fall through to YAML */ }
899
+ }
900
+
967
901
  const runnerPath = path.join(configRoot, configPrefix, "task-runner.yaml");
968
902
  if (!fs.existsSync(runnerPath)) return { paths: [], contexts: [], areaRepoIds: {} };
969
903
 
@@ -1132,9 +1066,10 @@ async function cmdUninstall(args) {
1132
1066
  console.log(`\n${c.bold}Taskplane Uninstall${c.reset}\n`);
1133
1067
 
1134
1068
  const managedFiles = [
1069
+ ".pi/taskplane-config.json",
1070
+ ".pi/taskplane.json",
1135
1071
  ".pi/task-runner.yaml",
1136
1072
  ".pi/task-orchestrator.yaml",
1137
- ".pi/taskplane.json",
1138
1073
  ".pi/agents/task-worker.md",
1139
1074
  ".pi/agents/task-reviewer.md",
1140
1075
  ".pi/agents/task-merger.md",
@@ -1194,7 +1129,7 @@ async function cmdUninstall(args) {
1194
1129
  for (const f of sidecarsToDelete) console.log(` - remove ${f.rel}`);
1195
1130
  for (const d of taskDirsToDelete) console.log(` - remove dir ${d.rel}`);
1196
1131
  if (removeTasks && taskDirsToDelete.length === 0) {
1197
- console.log(` ${c.dim}No task area directories found from .pi/task-runner.yaml.${c.reset}`);
1132
+ console.log(` ${c.dim}No task area directories found in config.${c.reset}`);
1198
1133
  }
1199
1134
  if (!removeTasks) {
1200
1135
  console.log(` ${c.dim}Task directories are preserved by default (use --remove-tasks to delete them).${c.reset}`);
@@ -1637,6 +1572,10 @@ async function cmdInit(args) {
1637
1572
  const tasksRootIdx = args.indexOf("--tasks-root");
1638
1573
  const tasksRootRaw = tasksRootIdx !== -1 ? args[tasksRootIdx + 1] : null;
1639
1574
 
1575
+ if (preset && preset !== "minimal" && preset !== "full") {
1576
+ die(`Unknown preset: "${preset}". Valid presets are: minimal, full`);
1577
+ }
1578
+
1640
1579
  if (noExamplesFlag && includeExamples) {
1641
1580
  die("Choose either --no-examples or --include-examples, not both.");
1642
1581
  }
@@ -1670,7 +1609,7 @@ async function cmdInit(args) {
1670
1609
 
1671
1610
  // ── Mode auto-detection ──────────────────────────────────────
1672
1611
  const detection = detectInitMode(projectRoot);
1673
- const isPreset = preset === "minimal" || preset === "full" || preset === "runner-only";
1612
+ const isPreset = preset === "minimal" || preset === "full";
1674
1613
 
1675
1614
  // Error path: not a git repo and no git repos found
1676
1615
  if (detection.mode === "error") {
@@ -1911,7 +1850,7 @@ async function cmdInit(args) {
1911
1850
 
1912
1851
  // ── Gather config values (workspace mode) ───────────────────
1913
1852
  let vars;
1914
- if (preset === "minimal" || preset === "full" || preset === "runner-only") {
1853
+ if (preset === "minimal" || preset === "full") {
1915
1854
  vars = getPresetVars(preset, projectRoot, tasksRootOverride);
1916
1855
  console.log(` Using preset: ${c.cyan}${preset}${c.reset}`);
1917
1856
  if (tasksRootOverride) {
@@ -1955,22 +1894,6 @@ async function cmdInit(args) {
1955
1894
  );
1956
1895
  }
1957
1896
 
1958
- // Task runner config
1959
- writeFile(
1960
- path.join(taskplaneDir, "task-runner.yaml"),
1961
- generateTaskRunnerYaml(vars),
1962
- { skipIfExists, label: `${configRepoName}/.taskplane/task-runner.yaml` }
1963
- );
1964
-
1965
- // Orchestrator config (skip for runner-only preset)
1966
- if (preset !== "runner-only") {
1967
- writeFile(
1968
- path.join(taskplaneDir, "task-orchestrator.yaml"),
1969
- generateOrchestratorYaml(vars),
1970
- { skipIfExists, label: `${configRepoName}/.taskplane/task-orchestrator.yaml` }
1971
- );
1972
- }
1973
-
1974
1897
  // Project config JSON (taskplane-config.json)
1975
1898
  const projectConfig = generateProjectConfig(vars, initAgentConfig);
1976
1899
  writeFile(
@@ -2109,10 +2032,8 @@ async function cmdInit(args) {
2109
2032
  console.log(` git push && ${c.dim}[create PR / merge to default branch]${c.reset}\n`);
2110
2033
  console.log(`${c.bold}Quick start:${c.reset}`);
2111
2034
  console.log(` ${c.cyan}pi${c.reset} # start pi (taskplane auto-loads)`);
2112
- if (preset !== "runner-only") {
2113
- console.log(` ${c.cyan}/orch${c.reset} # start the taskplane supervisor`);
2114
- console.log(` ${c.cyan}/orch all${c.reset} # run all open tasks`);
2115
- }
2035
+ console.log(` ${c.cyan}/orch${c.reset} # start the taskplane supervisor`);
2036
+ console.log(` ${c.cyan}/orch all${c.reset} # run all open tasks`);
2116
2037
  if (inferTaskplaneInstallScope() === "global") {
2117
2038
  console.log(` ${c.cyan}taskplane config --save-as-defaults${c.reset} # save these agent defaults for future inits`);
2118
2039
  }
@@ -2139,7 +2060,7 @@ async function cmdInit(args) {
2139
2060
 
2140
2061
  // Gather config values
2141
2062
  let vars;
2142
- if (preset === "minimal" || preset === "full" || preset === "runner-only") {
2063
+ if (preset === "minimal" || preset === "full") {
2143
2064
  vars = getPresetVars(preset, projectRoot, tasksRootOverride);
2144
2065
  console.log(` Using preset: ${c.cyan}${preset}${c.reset}`);
2145
2066
  if (tasksRootOverride) {
@@ -2180,23 +2101,7 @@ async function cmdInit(args) {
2180
2101
  );
2181
2102
  }
2182
2103
 
2183
- // Task runner config
2184
- writeFile(
2185
- path.join(projectRoot, ".pi", "task-runner.yaml"),
2186
- generateTaskRunnerYaml(vars),
2187
- { skipIfExists, label: ".pi/task-runner.yaml" }
2188
- );
2189
-
2190
- // Orchestrator config (skip for runner-only preset)
2191
- if (preset !== "runner-only") {
2192
- writeFile(
2193
- path.join(projectRoot, ".pi", "task-orchestrator.yaml"),
2194
- generateOrchestratorYaml(vars),
2195
- { skipIfExists, label: ".pi/task-orchestrator.yaml" }
2196
- );
2197
- }
2198
-
2199
- // Unified project config JSON
2104
+ // Project config JSON
2200
2105
  writeFile(
2201
2106
  path.join(projectRoot, ".pi", "taskplane-config.json"),
2202
2107
  JSON.stringify(generateProjectConfig(vars, initAgentConfig), null, 2) + "\n",
@@ -2271,10 +2176,8 @@ async function cmdInit(args) {
2271
2176
  console.log(`\n${OK} ${c.bold}Taskplane initialized!${c.reset}\n`);
2272
2177
  console.log(`${c.bold}Quick start:${c.reset}`);
2273
2178
  console.log(` ${c.cyan}pi${c.reset} # start pi (taskplane auto-loads)`);
2274
- if (preset !== "runner-only") {
2275
- console.log(` ${c.cyan}/orch${c.reset} # start the taskplane supervisor`);
2276
- console.log(` ${c.cyan}/orch all${c.reset} # run all open tasks`);
2277
- }
2179
+ console.log(` ${c.cyan}/orch${c.reset} # start the taskplane supervisor`);
2180
+ console.log(` ${c.cyan}/orch all${c.reset} # run all open tasks`);
2278
2181
  if (inferTaskplaneInstallScope() === "global") {
2279
2182
  console.log(` ${c.cyan}taskplane config --save-as-defaults${c.reset} # save these agent defaults for future inits`);
2280
2183
  }
@@ -2341,10 +2244,8 @@ function printFileList(vars, noExamples, preset, exampleTemplateDirs = [], proje
2341
2244
  ".pi/agents/task-reviewer.md",
2342
2245
  ".pi/agents/task-merger.md",
2343
2246
  ".pi/agents/supervisor.md",
2344
- ".pi/task-runner.yaml",
2247
+ ".pi/taskplane-config.json",
2345
2248
  ];
2346
- if (preset !== "runner-only") files.push(".pi/task-orchestrator.yaml");
2347
- files.push(".pi/taskplane-config.json");
2348
2249
  files.push(".pi/taskplane.json");
2349
2250
  files.push(`${vars.tasks_root}/CONTEXT.md`);
2350
2251
  if (!noExamples) {
@@ -2380,10 +2281,8 @@ function printWorkspaceFileList(vars, noExamples, preset, exampleTemplateDirs, c
2380
2281
  `${prefix}/agents/task-reviewer.md`,
2381
2282
  `${prefix}/agents/task-merger.md`,
2382
2283
  `${prefix}/agents/supervisor.md`,
2383
- `${prefix}/task-runner.yaml`,
2284
+ `${prefix}/taskplane-config.json`,
2384
2285
  ];
2385
- if (preset !== "runner-only") files.push(`${prefix}/task-orchestrator.yaml`);
2386
- files.push(`${prefix}/taskplane-config.json`);
2387
2286
  files.push(`${prefix}/taskplane.json`);
2388
2287
  files.push(`${prefix}/workspace.json`);
2389
2288
  files.push(`${configRepoName}/${vars.tasks_root}/CONTEXT.md`);
@@ -2883,15 +2782,20 @@ function cmdDoctor() {
2883
2782
  // Check project config (common — both modes)
2884
2783
  console.log();
2885
2784
  const hasUnifiedJson = fs.existsSync(path.join(configLocation.root, configLocation.prefix, "taskplane-config.json"));
2785
+ const hasYamlFallback = !hasUnifiedJson && (
2786
+ fs.existsSync(path.join(configLocation.root, configLocation.prefix, "task-runner.yaml")) ||
2787
+ fs.existsSync(path.join(configLocation.root, configLocation.prefix, "task-orchestrator.yaml"))
2788
+ );
2886
2789
  const configFiles = [
2887
- { path: "taskplane-config.json", required: false, hide: false },
2888
- // YAML configs are legacy fallback — hide when taskplane-config.json exists
2889
- { path: "task-runner.yaml", required: !hasUnifiedJson, hide: hasUnifiedJson },
2890
- { path: "task-orchestrator.yaml", required: !hasUnifiedJson, hide: hasUnifiedJson },
2790
+ // JSON is required unless legacy YAML exists as fallback
2791
+ { path: "taskplane-config.json", required: !hasYamlFallback, hide: false },
2792
+ // YAML configs are legacy fallback only shown when JSON config is missing
2793
+ { path: "task-runner.yaml", required: false, hide: hasUnifiedJson },
2794
+ { path: "task-orchestrator.yaml", required: false, hide: hasUnifiedJson },
2891
2795
  { path: "agents/task-worker.md", required: true, hide: false },
2892
2796
  { path: "agents/task-reviewer.md", required: true, hide: false },
2893
2797
  { path: "agents/task-merger.md", required: true, hide: false },
2894
- // supervisor.md is created by /orch; taskplane.json is created at runtime
2798
+ // supervisor.md is optional (scaffolded by init but may be absent in older projects); taskplane.json is created at runtime
2895
2799
  { path: "agents/supervisor.md", required: false, hide: true },
2896
2800
  { path: "taskplane.json", required: false, hide: true },
2897
2801
  ];
@@ -2979,7 +2883,7 @@ function cmdDoctor() {
2979
2883
  console.log(` ${OK} area '${areaName}' repo_id: ${repoId}`);
2980
2884
  } else {
2981
2885
  console.log(` ${FAIL} area '${areaName}' repo_id '${repoId}' does not match any workspace repo [AREA_REPO_ID_UNKNOWN]`);
2982
- console.log(` ${c.dim}→ Available repos: ${knownRepoIds.join(", ")}. Fix repo_id in ${configLocation.label}/task-runner.yaml${c.reset}`);
2886
+ console.log(` ${c.dim}→ Available repos: ${knownRepoIds.join(", ")}. Fix repoId in ${configLocation.label}/taskplane-config.json${c.reset}`);
2983
2887
  issues++;
2984
2888
  }
2985
2889
  }
@@ -3239,7 +3143,7 @@ ${c.bold}Commands:${c.reset}
3239
3143
  ${c.cyan}help${c.reset} Show this help message
3240
3144
 
3241
3145
  ${c.bold}Init options:${c.reset}
3242
- --preset <name> Use a preset: minimal, full, runner-only
3146
+ --preset <name> Use a preset: minimal, full
3243
3147
  --tasks-root <path> Relative tasks directory to use (e.g. docs/task-management)
3244
3148
  --no-examples Skip example tasks scaffolding
3245
3149
  --include-examples With --tasks-root, include example tasks (default is skip)
@@ -3261,7 +3165,7 @@ ${c.bold}Uninstall options:${c.reset}
3261
3165
  --package-only Only remove installed package (skip project cleanup)
3262
3166
  --local Force package uninstall from project-local scope
3263
3167
  --global Force package uninstall from global scope
3264
- --remove-tasks Also remove task area directories from task-runner.yaml
3168
+ --remove-tasks Also remove task area directories
3265
3169
  --all Equivalent to --package + --remove-tasks
3266
3170
 
3267
3171
  ${c.bold}Examples:${c.reset}
@@ -180,9 +180,9 @@ function showCopyToast(text) {
180
180
  }
181
181
 
182
182
  function copySessionId(sessionName) {
183
+ // Retained for potential future use but no longer rendered in the UI.
183
184
  navigator.clipboard.writeText(sessionName).then(() => {
184
185
  showCopyToast(`session ${sessionName}`);
185
- // Flash the button
186
186
  const btn = document.querySelector(`[data-session="${sessionName}"]`);
187
187
  if (btn) {
188
188
  btn.classList.add("copied");
@@ -455,6 +455,27 @@ function renderSummary(batch) {
455
455
  const wavePlan = batch.wavePlan || [tasks.map(t => t.taskId)]; // fallback: single wave
456
456
  const currentWaveIdx = batch.currentWaveIndex || 0;
457
457
 
458
+ // TP-148: Build wave segment context — for each task appearing in multiple waves,
459
+ // determine which segment corresponds to each wave appearance.
460
+ const taskWaveAppearance = new Map(); // taskId → count of appearances so far
461
+ const waveSegmentLabels = wavePlan.map((taskIds) => {
462
+ const labels = new Map(); // taskId → label string
463
+ for (const tid of taskIds) {
464
+ const task = taskMap.get(tid);
465
+ const segmentIds = task?.segmentIds;
466
+ if (!segmentIds || segmentIds.length <= 1) continue;
467
+ const count = (taskWaveAppearance.get(tid) || 0);
468
+ taskWaveAppearance.set(tid, count + 1);
469
+ const segId = segmentIds[count];
470
+ if (segId) {
471
+ const parsed = parseSegmentId(segId);
472
+ const repo = parsed ? parsed.repoId : "";
473
+ labels.set(tid, `${tid} (segment ${count + 1}/${segmentIds.length}: ${repo})`);
474
+ }
475
+ }
476
+ return labels;
477
+ });
478
+
458
479
  // Compute per-wave and overall checkbox totals
459
480
  let batchChecked = 0, batchTotal = 0;
460
481
  const waveStats = wavePlan.map((taskIds, waveIdx) => {
@@ -503,7 +524,10 @@ function renderSummary(batch) {
503
524
  const fillWidth = isDone ? 100 : fillPct;
504
525
  const segClass = isCurrent ? "wave-seg-current" : isFuture ? "wave-seg-future" : "";
505
526
 
506
- barHtml += `<div class="wave-seg ${segClass}" style="width:${segWidthPct.toFixed(1)}%" title="W${ws.waveIdx + 1}: ${ws.checked}/${ws.total} checkboxes (${ws.taskIds.join(', ')})">`;
527
+ // TP-148: Use segment-aware labels in tooltip when available
528
+ const segLabels = waveSegmentLabels[ws.waveIdx] || new Map();
529
+ const tooltipTasks = ws.taskIds.map(tid => segLabels.get(tid) || tid).join(', ');
530
+ barHtml += `<div class="wave-seg ${segClass}" style="width:${segWidthPct.toFixed(1)}%" title="W${ws.waveIdx + 1}: ${ws.checked}/${ws.total} checkboxes (${tooltipTasks})">`;
507
531
  barHtml += ` <div class="wave-seg-fill ${fillClass}" style="width:${fillWidth.toFixed(1)}%"></div>`;
508
532
  barHtml += ` <span class="wave-seg-label">W${ws.waveIdx + 1}</span>`;
509
533
  barHtml += `</div>`;
@@ -625,7 +649,7 @@ function renderLanesTasks(batch, sessions) {
625
649
  const laneSessionId = lane.laneSessionId;
626
650
  const v2Alive = isLaneAliveV2(lane.laneNumber);
627
651
  const alive = v2Alive !== null ? v2Alive : sessionSet.has(laneSessionId);
628
- const sessionChip = `session: ${laneSessionId}`;
652
+
629
653
 
630
654
  // Lane header
631
655
  html += `<div class="lane-group">`;
@@ -646,11 +670,7 @@ function renderLanesTasks(batch, sessions) {
646
670
  // View button: shows conversation stream when available
647
671
  const isViewingConv = viewerMode === 'conversation' && viewerTarget === laneSessionId;
648
672
  html += ` <button class="session-view-btn${isViewingConv ? ' active' : ''}" onclick="viewConversation('${escapeHtml(laneSessionId)}')" title="View worker conversation">👁 View</button>`;
649
- if (alive) {
650
- html += ` <span class="session-cmd" data-session="${escapeHtml(laneSessionId)}" onclick="copySessionId('${escapeHtml(laneSessionId)}')" title="Copy session ID">${escapeHtml(sessionChip)}</span>`;
651
- } else {
652
- html += ` <span class="session-cmd dead-session">${escapeHtml(sessionChip)}</span>`;
653
- }
673
+
654
674
  html += ` </div>`;
655
675
  html += `</div>`;
656
676
 
@@ -975,12 +995,7 @@ function renderMergeAgents(batch, sessions) {
975
995
  // Full telemetry cell
976
996
  html += `<td class="merge-telemetry-cell">${mergeTelemetryHtml(mergeTel, effectiveAlive)}</td>`;
977
997
  html += `<td>`;
978
- if (effectiveAlive) {
979
- const sessionChip = `session: ${effectiveSession}`;
980
- html += `<span class="session-cmd" data-session="${escapeHtml(effectiveSession)}" onclick="copySessionId('${escapeHtml(effectiveSession)}')" title="Copy session ID">${escapeHtml(sessionChip)}</span>`;
981
- } else {
982
- html += '<span class="merge-no-data">—</span>';
983
- }
998
+ html += '<span class="merge-no-data">—</span>';
984
999
  html += `</td>`;
985
1000
  html += `<td class="merge-detail-cell">${mr.failureReason ? escapeHtml(mr.failureReason) : "—"}</td>`;
986
1001
  html += `</tr>`;
@@ -1015,14 +1030,13 @@ function renderMergeAgents(batch, sessions) {
1015
1030
  if (shownSessions.has(sess)) continue;
1016
1031
 
1017
1032
  const sessTel = telemetry[sess] || null;
1018
- const sessionChip = `session: ${sess}`;
1019
1033
  html += `<tr>`;
1020
1034
  html += `<td class="merge-wave-cell">—</td>`;
1021
1035
  html += `<td><span class="status-badge status-running"><span class="status-dot running"></span> merging</span></td>`;
1022
1036
  html += `<td class="merge-session-cell">${escapeHtml(sess)}</td>`;
1023
1037
  // Full telemetry cell for active merge session
1024
1038
  html += `<td class="merge-telemetry-cell">${mergeTelemetryHtml(sessTel, true)}</td>`;
1025
- html += `<td><span class="session-cmd" data-session="${escapeHtml(sess)}" onclick="copySessionId('${escapeHtml(sess)}')" title="Copy session ID">${escapeHtml(sessionChip)}</span></td>`;
1039
+ html += `<td>—</td>`;
1026
1040
  html += `<td>—</td>`;
1027
1041
  html += `</tr>`;
1028
1042
  }
@@ -1144,7 +1158,7 @@ function renderMailboxAuditEvent(evt) {
1144
1158
  } else {
1145
1159
  // Unknown event type — render generically
1146
1160
  direction = evt.from ? `${escapeHtml(evt.from)}` : '';
1147
- preview = JSON.stringify(evt).slice(0, 120);
1161
+ preview = JSON.stringify(evt);
1148
1162
  }
1149
1163
 
1150
1164
  return `<div class="message-row">`
@@ -1175,7 +1189,7 @@ function renderMailboxDirMessage(msg) {
1175
1189
  else if (msg._status === 'reply-acked') statusBadge = '<span class="msg-badge msg-delivered">reply (acked)</span>';
1176
1190
  else statusBadge = '';
1177
1191
  const typeBadge = `<span class="msg-badge msg-type">${escapeHtml(msg.type || '')}</span>`;
1178
- const preview = (msg.content || '').slice(0, 120);
1192
+ const preview = msg.content || '';
1179
1193
  const broadcastTag = msg._isBroadcast ? ' <span class="msg-badge msg-type">broadcast</span>' : '';
1180
1194
 
1181
1195
  return `<div class="message-row">`
@@ -1787,9 +1787,9 @@ body {
1787
1787
  }
1788
1788
  .message-row {
1789
1789
  display: flex;
1790
- align-items: center;
1790
+ align-items: flex-start;
1791
1791
  gap: 8px;
1792
- padding: 4px 8px;
1792
+ padding: 6px 8px;
1793
1793
  font-size: 0.8rem;
1794
1794
  border-radius: 4px;
1795
1795
  background: var(--bg-secondary);
@@ -1826,9 +1826,8 @@ body {
1826
1826
  }
1827
1827
  .msg-preview {
1828
1828
  color: var(--text-primary);
1829
- overflow: hidden;
1830
- text-overflow: ellipsis;
1831
- white-space: nowrap;
1829
+ white-space: pre-wrap;
1830
+ word-break: break-word;
1832
1831
  flex: 1;
1833
1832
  }
1834
1833
  .msg-rate-limited {