taskplane 0.1.11 → 0.1.12

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,2 +1,180 @@
1
- # taskplane
2
- Multi-agent AI orchestration system designed to drive successful coding outcomes with a high level of transparency (think more light-factory than dark-factory).
1
+ # Taskplane
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.
4
+
5
+ > **Status:** Experimental / Early — APIs and config formats may change between releases.
6
+
7
+ ## What It Does
8
+
9
+ Taskplane turns your coding project into an AI-managed task board. You define tasks as structured markdown files. Taskplane's agents execute them autonomously — one at a time with `/task`, or many in parallel with `/orch`.
10
+
11
+ ### Key Features
12
+
13
+ - **Task Runner** (`/task`) — Autonomous single-task execution. Workers run in fresh-context loops with STATUS.md as persistent memory. Every checkbox gets a git checkpoint. Cross-model reviewers catch what the worker missed.
14
+ - **Task Orchestrator** (`/orch`) — Parallel multi-task execution using git worktrees for full filesystem isolation. Dependency-aware wave scheduling. Automated merges with conflict resolution.
15
+ - **Web Dashboard** — Live browser-based monitoring via `taskplane dashboard`. SSE streaming, lane/task progress, wave visualization, batch history.
16
+ - **Structured Tasks** — PROMPT.md defines the mission, steps, and constraints. STATUS.md tracks progress. Agents follow the plan, not vibes.
17
+ - **Checkpoint Discipline** — Every completed checkbox item triggers a git commit. Work is never lost, even if a worker crashes mid-task.
18
+ - **Cross-Model Review** — Reviewer agent uses a different model than the worker. Independent quality gate before merge.
19
+
20
+ ## Install
21
+
22
+ Taskplane is a [pi package](https://github.com/badlogic/pi-mono). You need [Node.js](https://nodejs.org/) ≥ 20 and [pi](https://github.com/badlogic/pi-mono) installed first.
23
+
24
+ ### Option A: Global Install (all projects)
25
+
26
+ ```bash
27
+ pi install npm:taskplane
28
+ ```
29
+
30
+ ### Option B: Project-Local Install (recommended for teams)
31
+
32
+ ```bash
33
+ cd my-project
34
+ pi install -l npm:taskplane
35
+ ```
36
+
37
+ Then scaffold your project:
38
+
39
+ ```bash
40
+ taskplane init
41
+ ```
42
+
43
+ Verify the installation:
44
+
45
+ ```bash
46
+ taskplane doctor
47
+ ```
48
+
49
+ ## Quickstart
50
+
51
+ ### 1. Initialize a project
52
+
53
+ ```bash
54
+ cd my-project
55
+ taskplane init --preset full
56
+ ```
57
+
58
+ This creates config files in `.pi/`, agent prompts, and two example tasks.
59
+
60
+ ### 2. Launch the dashboard (recommended)
61
+
62
+ In a separate terminal:
63
+
64
+ ```bash
65
+ taskplane dashboard
66
+ ```
67
+
68
+ Opens a live web dashboard at `http://localhost:8099` with real-time batch monitoring.
69
+
70
+ ### 3. Run your first orchestration
71
+
72
+ ```bash
73
+ pi
74
+ ```
75
+
76
+ Inside the pi session:
77
+
78
+ ```
79
+ /orch-plan all # Preview waves, lanes, and dependencies
80
+ /orch all # Execute all pending tasks in parallel
81
+ /orch-status # Monitor batch progress
82
+ ```
83
+
84
+ The default scaffold includes two independent example tasks, so `/orch all` gives you an immediate orchestrator + dashboard experience.
85
+
86
+ ### 4. Optional: run one task directly
87
+
88
+ `/task` is still useful for single-task execution and focused debugging:
89
+
90
+ ```
91
+ /task taskplane-tasks/EXAMPLE-001-hello-world/PROMPT.md
92
+ /task-status
93
+ ```
94
+
95
+ Important distinction:
96
+
97
+ - `/task` runs in your **current branch/worktree**.
98
+ - `/orch` runs tasks in **isolated worktrees** and merges back.
99
+
100
+ 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:
101
+
102
+ ```text
103
+ /orch taskplane-tasks/EXAMPLE-001-hello-world/PROMPT.md
104
+ ```
105
+
106
+ Orchestrator lanes execute tasks through task-runner under the hood, so `/task` and `/orch` share the same core task execution model.
107
+
108
+ ## Commands
109
+
110
+ ### Pi Session Commands
111
+
112
+ | Command | Description |
113
+ |---------|-------------|
114
+ | `/task <path/to/PROMPT.md>` | Execute one task in the current branch/worktree |
115
+ | `/task-status` | Show current task progress |
116
+ | `/task-pause` | Pause after current worker iteration finishes |
117
+ | `/task-resume` | Resume a paused task |
118
+ | `/orch <areas\|paths\|all>` | Execute tasks via isolated worktrees (recommended default) |
119
+ | `/orch-plan <areas\|paths\|all>` | Preview execution plan without running |
120
+ | `/orch-status` | Show batch progress |
121
+ | `/orch-pause` | Pause batch after current tasks finish |
122
+ | `/orch-resume` | Resume a paused batch |
123
+ | `/orch-abort [--hard]` | Abort batch (graceful or immediate) |
124
+ | `/orch-deps <areas\|paths\|all>` | Show dependency graph |
125
+ | `/orch-sessions` | List active worker sessions |
126
+
127
+ ### CLI Commands
128
+
129
+ | Command | Description |
130
+ |---------|-------------|
131
+ | `taskplane init` | Scaffold project config (interactive or `--preset`) |
132
+ | `taskplane doctor` | Validate installation and config |
133
+ | `taskplane version` | Show version info |
134
+ | `taskplane dashboard` | Launch the web dashboard |
135
+ | `taskplane uninstall` | Remove Taskplane project files and optionally uninstall package (`--package`) |
136
+
137
+ ## How It Works
138
+
139
+ ```
140
+ ┌─────────────────────────────────────────────────────────────┐
141
+ │ ORCHESTRATOR (/orch) │
142
+ │ Parse tasks → Build dependency DAG → Compute waves │
143
+ │ Assign lanes → Spawn workers → Monitor → Merge │
144
+ └──────┬──────────┬──────────┬────────────────────────────────┘
145
+ │ │ │
146
+ ┌────▼────┐ ┌──▼─────┐ ┌──▼─────┐
147
+ │ Lane 1 │ │ Lane 2 │ │ Lane 3 │ ← Git worktrees
148
+ │ /task │ │ /task │ │ /task │ (isolated)
149
+ │ Worker │ │ Worker │ │ Worker │
150
+ │ Review │ │ Review │ │ Review │
151
+ └────┬────┘ └──┬─────┘ └──┬─────┘
152
+ │ │ │
153
+ └─────────┼──────────┘
154
+
155
+ ┌──────▼──────┐
156
+ │ Merge Agent │ ← Conflict resolution
157
+ │ Integration │ & verification
158
+ │ Branch │
159
+ └─────────────┘
160
+ ```
161
+
162
+ **Single task** (`/task`): Worker iterates in fresh-context loops. STATUS.md is persistent memory. Each checkbox → git checkpoint. Reviewer validates on completion.
163
+
164
+ **Parallel batch** (`/orch`): Tasks are sorted into dependency waves. Each wave runs in parallel across lanes (git worktrees). Completed lanes merge into the integration branch before the next wave starts.
165
+
166
+ ## Documentation
167
+
168
+ 📖 **[Full Documentation](docs/README.md)**
169
+
170
+ Start at the docs index for tutorials, how-to guides, reference docs, and architecture explanations.
171
+
172
+ ## Contributing
173
+
174
+ See **[CONTRIBUTING.md](CONTRIBUTING.md)** for development setup, testing, and contribution guidelines.
175
+
176
+ Maintainers: GitHub governance and branch protection guidance is in [docs/maintainers/repository-governance.md](docs/maintainers/repository-governance.md).
177
+
178
+ ## License
179
+
180
+ [MIT](LICENSE) © Henry Lach
package/bin/taskplane.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Taskplane CLI — Project scaffolding, diagnostics, and dashboard launcher.
4
+ * Taskplane CLI — Project scaffolding, diagnostics, uninstall, and dashboard launcher.
5
5
  *
6
6
  * This CLI handles what the pi package system cannot: project-local config
7
7
  * scaffolding, installation health checks, and dashboard management.
@@ -291,6 +291,238 @@ async function autoCommitTaskFiles(projectRoot, tasksRoot) {
291
291
  }
292
292
  }
293
293
 
294
+ function discoverTaskAreaPaths(projectRoot) {
295
+ const runnerPath = path.join(projectRoot, ".pi", "task-runner.yaml");
296
+ if (!fs.existsSync(runnerPath)) return [];
297
+
298
+ const raw = readYaml(runnerPath);
299
+ if (!raw) return [];
300
+
301
+ const lines = raw.split(/\r?\n/);
302
+ let inTaskAreas = false;
303
+ const paths = new Set();
304
+
305
+ for (const line of lines) {
306
+ const trimmed = line.trim();
307
+
308
+ if (!inTaskAreas) {
309
+ if (/^task_areas:\s*$/.test(trimmed)) {
310
+ inTaskAreas = true;
311
+ }
312
+ continue;
313
+ }
314
+
315
+ // End of task_areas block when we hit next top-level key
316
+ if (/^[A-Za-z0-9_]+\s*:\s*$/.test(line)) {
317
+ break;
318
+ }
319
+
320
+ const m = line.match(/^\s{4}path:\s*["']?([^"'\n#]+)["']?\s*(?:#.*)?$/);
321
+ if (m?.[1]) {
322
+ paths.add(m[1].trim());
323
+ }
324
+ }
325
+
326
+ return [...paths];
327
+ }
328
+
329
+ function pruneEmptyDir(dirPath) {
330
+ try {
331
+ if (!fs.existsSync(dirPath)) return false;
332
+ if (fs.readdirSync(dirPath).length !== 0) return false;
333
+ fs.rmdirSync(dirPath);
334
+ return true;
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
339
+
340
+ function listExampleTaskTemplates() {
341
+ const tasksTemplatesDir = path.join(TEMPLATES_DIR, "tasks");
342
+ try {
343
+ return fs.readdirSync(tasksTemplatesDir, { withFileTypes: true })
344
+ .filter((entry) => entry.isDirectory() && /^EXAMPLE-\d+/i.test(entry.name))
345
+ .map((entry) => entry.name)
346
+ .sort();
347
+ } catch {
348
+ return [];
349
+ }
350
+ }
351
+
352
+ async function cmdUninstall(args) {
353
+ const projectRoot = process.cwd();
354
+ const dryRun = args.includes("--dry-run");
355
+ const yes = args.includes("--yes") || args.includes("-y");
356
+ const removePackage = args.includes("--package") || args.includes("--all") || args.includes("--package-only");
357
+ const packageOnly = args.includes("--package-only");
358
+ const removeProject = !packageOnly;
359
+ const removeTasks = removeProject && (args.includes("--remove-tasks") || args.includes("--all"));
360
+ const local = args.includes("--local");
361
+ const global = args.includes("--global");
362
+
363
+ if (local && global) {
364
+ die("Choose either --local or --global, not both.");
365
+ }
366
+
367
+ console.log(`\n${c.bold}Taskplane Uninstall${c.reset}\n`);
368
+
369
+ const managedFiles = [
370
+ ".pi/task-runner.yaml",
371
+ ".pi/task-orchestrator.yaml",
372
+ ".pi/taskplane.json",
373
+ ".pi/agents/task-worker.md",
374
+ ".pi/agents/task-reviewer.md",
375
+ ".pi/agents/task-merger.md",
376
+ ".pi/batch-state.json",
377
+ ".pi/batch-history.json",
378
+ ".pi/orch-abort-signal",
379
+ ];
380
+
381
+ const sidecarPrefixes = [
382
+ "lane-state-",
383
+ "worker-conversation-",
384
+ "merge-result-",
385
+ "merge-request-",
386
+ ];
387
+
388
+ const filesToDelete = managedFiles
389
+ .map(rel => ({ rel, abs: path.join(projectRoot, rel) }))
390
+ .filter(({ abs }) => fs.existsSync(abs));
391
+
392
+ const piDir = path.join(projectRoot, ".pi");
393
+ const sidecarsToDelete = fs.existsSync(piDir)
394
+ ? fs.readdirSync(piDir)
395
+ .filter(name => sidecarPrefixes.some(prefix => name.startsWith(prefix)))
396
+ .map(name => ({ rel: path.join(".pi", name), abs: path.join(piDir, name) }))
397
+ : [];
398
+
399
+ let taskDirsToDelete = [];
400
+ if (removeTasks) {
401
+ const areaPaths = discoverTaskAreaPaths(projectRoot);
402
+ const rootPrefix = path.resolve(projectRoot) + path.sep;
403
+ taskDirsToDelete = areaPaths
404
+ .map(rel => ({ rel, abs: path.resolve(projectRoot, rel) }))
405
+ .filter(({ abs }) => abs.startsWith(rootPrefix) && fs.existsSync(abs));
406
+ }
407
+
408
+ const inferredInstallType = /[\\/]\.pi[\\/]/.test(PACKAGE_ROOT) ? "local" : "global";
409
+ const packageScope = local ? "local" : global ? "global" : inferredInstallType;
410
+ const piRemoveCmd = packageScope === "local"
411
+ ? "pi remove -l npm:taskplane"
412
+ : "pi remove npm:taskplane";
413
+
414
+ if (!removeProject && !removePackage) {
415
+ console.log(` ${WARN} Nothing to do. Use one of:`);
416
+ console.log(` ${c.cyan}taskplane uninstall${c.reset} # remove project-scaffolded files`);
417
+ console.log(` ${c.cyan}taskplane uninstall --package${c.reset} # remove installed package via pi`);
418
+ console.log();
419
+ return;
420
+ }
421
+
422
+ if (removeProject) {
423
+ console.log(`${c.bold}Project cleanup:${c.reset}`);
424
+ if (filesToDelete.length === 0 && sidecarsToDelete.length === 0 && taskDirsToDelete.length === 0) {
425
+ console.log(` ${c.dim}No Taskplane-managed project files found.${c.reset}`);
426
+ }
427
+ for (const f of filesToDelete) console.log(` - remove ${f.rel}`);
428
+ for (const f of sidecarsToDelete) console.log(` - remove ${f.rel}`);
429
+ for (const d of taskDirsToDelete) console.log(` - remove dir ${d.rel}`);
430
+ if (removeTasks && taskDirsToDelete.length === 0) {
431
+ console.log(` ${c.dim}No task area directories found from .pi/task-runner.yaml.${c.reset}`);
432
+ }
433
+ if (!removeTasks) {
434
+ console.log(` ${c.dim}Task directories are preserved by default (use --remove-tasks to delete them).${c.reset}`);
435
+ }
436
+ console.log();
437
+ }
438
+
439
+ if (removePackage) {
440
+ console.log(`${c.bold}Package cleanup:${c.reset}`);
441
+ console.log(` - run ${piRemoveCmd}`);
442
+ console.log(` ${c.dim}(removes extensions, skills, and dashboard files from this install scope)${c.reset}`);
443
+ console.log();
444
+ }
445
+
446
+ if (dryRun) {
447
+ console.log(`${INFO} Dry run complete. No files were changed.\n`);
448
+ return;
449
+ }
450
+
451
+ if (!yes) {
452
+ const proceed = await confirm("Proceed with uninstall?", false);
453
+ if (!proceed) {
454
+ console.log(" Aborted.");
455
+ return;
456
+ }
457
+ if (removeTasks) {
458
+ const taskConfirm = await confirm("This will delete task area directories recursively. Continue?", false);
459
+ if (!taskConfirm) {
460
+ console.log(" Aborted.");
461
+ return;
462
+ }
463
+ }
464
+ }
465
+
466
+ let removedCount = 0;
467
+ let failedCount = 0;
468
+
469
+ if (removeProject) {
470
+ for (const item of [...filesToDelete, ...sidecarsToDelete]) {
471
+ try {
472
+ fs.unlinkSync(item.abs);
473
+ removedCount++;
474
+ } catch (err) {
475
+ failedCount++;
476
+ console.log(` ${WARN} Failed to remove ${item.rel}: ${err.message}`);
477
+ }
478
+ }
479
+
480
+ for (const dir of taskDirsToDelete) {
481
+ try {
482
+ fs.rmSync(dir.abs, { recursive: true, force: true });
483
+ removedCount++;
484
+ } catch (err) {
485
+ failedCount++;
486
+ console.log(` ${WARN} Failed to remove directory ${dir.rel}: ${err.message}`);
487
+ }
488
+ }
489
+
490
+ // Best-effort cleanup of empty folders
491
+ pruneEmptyDir(path.join(projectRoot, ".pi", "agents"));
492
+ pruneEmptyDir(path.join(projectRoot, ".pi"));
493
+ }
494
+
495
+ if (removePackage) {
496
+ if (!commandExists("pi")) {
497
+ failedCount++;
498
+ console.log(` ${FAIL} pi is not on PATH; could not run: ${piRemoveCmd}`);
499
+ } else {
500
+ try {
501
+ execSync(piRemoveCmd, { cwd: projectRoot, stdio: "inherit" });
502
+ } catch {
503
+ failedCount++;
504
+ console.log(` ${FAIL} Package uninstall failed: ${piRemoveCmd}`);
505
+ }
506
+ }
507
+ }
508
+
509
+ console.log();
510
+ if (failedCount === 0) {
511
+ console.log(`${OK} ${c.bold}Uninstall complete.${c.reset}`);
512
+ if (removeProject) {
513
+ console.log(` Removed ${removedCount} project artifact(s).`);
514
+ }
515
+ console.log();
516
+ } else {
517
+ console.log(`${FAIL} Uninstall completed with ${failedCount} error(s).`);
518
+ if (removeProject) {
519
+ console.log(` Removed ${removedCount} project artifact(s).`);
520
+ }
521
+ console.log();
522
+ process.exit(1);
523
+ }
524
+ }
525
+
294
526
  // ─── init ───────────────────────────────────────────────────────────────────
295
527
 
296
528
  async function cmdInit(args) {
@@ -326,9 +558,11 @@ async function cmdInit(args) {
326
558
  vars = await getInteractiveVars(projectRoot);
327
559
  }
328
560
 
561
+ const exampleTemplateDirs = noExamples ? [] : listExampleTaskTemplates();
562
+
329
563
  if (dryRun) {
330
564
  console.log(`\n${c.bold}Dry run — files that would be created:${c.reset}\n`);
331
- printFileList(vars, noExamples, preset);
565
+ printFileList(vars, noExamples, preset, exampleTemplateDirs);
332
566
  return;
333
567
  }
334
568
 
@@ -382,37 +616,41 @@ async function cmdInit(args) {
382
616
  { skipIfExists, label: `${vars.tasks_root}/CONTEXT.md` }
383
617
  );
384
618
 
385
- // Example task
619
+ // Example tasks
386
620
  if (!noExamples) {
387
- const exampleDir = path.join(TEMPLATES_DIR, "tasks", "EXAMPLE-001-hello-world");
388
- const destDir = path.join(projectRoot, vars.tasks_root, "EXAMPLE-001-hello-world");
389
- for (const file of ["PROMPT.md", "STATUS.md"]) {
390
- const src = fs.readFileSync(path.join(exampleDir, file), "utf-8");
391
- writeFile(path.join(destDir, file), interpolate(src, vars), {
392
- skipIfExists,
393
- label: `${vars.tasks_root}/EXAMPLE-001-hello-world/${file}`,
394
- });
621
+ for (const exampleName of exampleTemplateDirs) {
622
+ const exampleDir = path.join(TEMPLATES_DIR, "tasks", exampleName);
623
+ const destDir = path.join(projectRoot, vars.tasks_root, exampleName);
624
+ for (const file of ["PROMPT.md", "STATUS.md"]) {
625
+ const srcPath = path.join(exampleDir, file);
626
+ if (!fs.existsSync(srcPath)) continue;
627
+ const src = fs.readFileSync(srcPath, "utf-8");
628
+ writeFile(path.join(destDir, file), interpolate(src, vars), {
629
+ skipIfExists,
630
+ label: `${vars.tasks_root}/${exampleName}/${file}`,
631
+ });
632
+ }
633
+ }
634
+ if (exampleTemplateDirs.length === 0) {
635
+ console.log(` ${WARN} No example task templates found under templates/tasks/EXAMPLE-*`);
395
636
  }
396
637
  }
397
638
 
398
639
  // Auto-commit task files to git so they're available in worktrees
399
- if (!dryRun) {
400
- await autoCommitTaskFiles(projectRoot, vars.tasks_root);
401
- }
640
+ await autoCommitTaskFiles(projectRoot, vars.tasks_root);
402
641
 
403
642
  // Report
404
643
  console.log(`\n${OK} ${c.bold}Taskplane initialized!${c.reset}\n`);
405
644
  console.log(`${c.bold}Quick start:${c.reset}`);
406
645
  console.log(` ${c.cyan}pi${c.reset} # start pi (taskplane auto-loads)`);
407
- if (!noExamples) {
408
- console.log(
409
- ` ${c.cyan}/task ${vars.tasks_root}/EXAMPLE-001-hello-world/PROMPT.md${c.reset} # run the example task`
410
- );
411
- }
412
646
  if (preset !== "runner-only") {
413
- console.log(
414
- ` ${c.cyan}/orch all${c.reset} # orchestrate all pending tasks`
415
- );
647
+ console.log(` ${c.cyan}/orch-plan all${c.reset} # preview waves/lanes/dependencies`);
648
+ console.log(` ${c.cyan}/orch all${c.reset} # run examples via orchestrator`);
649
+ }
650
+ if (!noExamples && exampleTemplateDirs.length > 0) {
651
+ const firstExample = exampleTemplateDirs[0];
652
+ console.log(` ${c.dim}optional single-task mode:${c.reset}`);
653
+ console.log(` ${c.cyan}/task ${vars.tasks_root}/${firstExample}/PROMPT.md${c.reset}`);
416
654
  }
417
655
  console.log();
418
656
  }
@@ -465,7 +703,7 @@ async function getInteractiveVars(projectRoot) {
465
703
  };
466
704
  }
467
705
 
468
- function printFileList(vars, noExamples, preset) {
706
+ function printFileList(vars, noExamples, preset, exampleTemplateDirs = []) {
469
707
  const files = [
470
708
  ".pi/agents/task-worker.md",
471
709
  ".pi/agents/task-reviewer.md",
@@ -476,8 +714,10 @@ function printFileList(vars, noExamples, preset) {
476
714
  files.push(".pi/taskplane.json");
477
715
  files.push(`${vars.tasks_root}/CONTEXT.md`);
478
716
  if (!noExamples) {
479
- files.push(`${vars.tasks_root}/EXAMPLE-001-hello-world/PROMPT.md`);
480
- files.push(`${vars.tasks_root}/EXAMPLE-001-hello-world/STATUS.md`);
717
+ for (const exampleName of exampleTemplateDirs) {
718
+ files.push(`${vars.tasks_root}/${exampleName}/PROMPT.md`);
719
+ files.push(`${vars.tasks_root}/${exampleName}/STATUS.md`);
720
+ }
481
721
  }
482
722
  for (const f of files) console.log(` ${c.green}create${c.reset} ${f}`);
483
723
  console.log();
@@ -690,11 +930,12 @@ ${c.bold}Commands:${c.reset}
690
930
  ${c.cyan}doctor${c.reset} Validate installation and project configuration
691
931
  ${c.cyan}version${c.reset} Show version information
692
932
  ${c.cyan}dashboard${c.reset} Launch the web-based orchestrator dashboard
933
+ ${c.cyan}uninstall${c.reset} Remove Taskplane project files and/or package install
693
934
  ${c.cyan}help${c.reset} Show this help message
694
935
 
695
936
  ${c.bold}Init options:${c.reset}
696
937
  --preset <name> Use a preset: minimal, full, runner-only
697
- --no-examples Skip example task scaffolding
938
+ --no-examples Skip example tasks scaffolding
698
939
  --force Overwrite existing files without prompting
699
940
  --dry-run Show what would be created without writing
700
941
 
@@ -702,13 +943,25 @@ ${c.bold}Dashboard options:${c.reset}
702
943
  --port <number> Port to listen on (default: 8099)
703
944
  --no-open Don't auto-open browser
704
945
 
946
+ ${c.bold}Uninstall options:${c.reset}
947
+ --dry-run Show what would be removed
948
+ --yes, -y Skip confirmation prompts
949
+ --package Also remove installed package via pi remove
950
+ --package-only Only remove installed package (skip project cleanup)
951
+ --local Force package uninstall from project-local scope
952
+ --global Force package uninstall from global scope
953
+ --remove-tasks Also remove task area directories from task-runner.yaml
954
+ --all Equivalent to --package + --remove-tasks
955
+
705
956
  ${c.bold}Examples:${c.reset}
706
- taskplane init # Interactive project setup
707
- taskplane init --preset full # Quick setup with defaults
708
- taskplane init --dry-run # Preview what would be created
709
- taskplane doctor # Check installation health
710
- taskplane dashboard # Launch web dashboard
711
- taskplane dashboard --port 3000 # Dashboard on custom port
957
+ taskplane init # Interactive project setup
958
+ taskplane init --preset full # Quick setup with defaults
959
+ taskplane init --dry-run # Preview what would be created
960
+ taskplane doctor # Check installation health
961
+ taskplane dashboard # Launch web dashboard
962
+ taskplane dashboard --port 3000 # Dashboard on custom port
963
+ taskplane uninstall --dry-run # Preview uninstall actions
964
+ taskplane uninstall --package --yes # Remove project files + package install
712
965
 
713
966
  ${c.bold}Getting started:${c.reset}
714
967
  1. pi install npm:taskplane # Install the pi package
@@ -738,6 +991,9 @@ switch (command) {
738
991
  case "dashboard":
739
992
  cmdDashboard(args);
740
993
  break;
994
+ case "uninstall":
995
+ await cmdUninstall(args);
996
+ break;
741
997
  case "help":
742
998
  case "--help":
743
999
  case "-h":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -73,8 +73,8 @@ Use the source branch and merge message from the merge request.
73
73
  Run each verification command from the merge request. Typical commands:
74
74
 
75
75
  ```bash
76
- go build ./... # All services compile
77
- cd web && npm run type-check # Frontend types valid
76
+ npm test # Unit/integration checks
77
+ npm run build # Build/compile checks
78
78
  ```
79
79
 
80
80
  **If verification passes:** Write result with `status: "SUCCESS"` (or
@@ -95,29 +95,29 @@ Write a `BUILD_FAILURE` result with the error output from the failed command.
95
95
  | Different files modified | N/A (git handles automatically) | No action needed |
96
96
  | Same file, different sections | Yes — accept both changes | Edit file to include both changes, remove conflict markers |
97
97
  | Same file, same lines | **No** — needs human review | Abort merge immediately |
98
- | Generated files (`go.sum`, `package-lock.json`) | Yes — regenerate | Run `go mod tidy` / `npm install` to regenerate |
99
- | `STATUS.md` / `.DONE` files | Yes — keep both | Accept the incoming (theirs) version for STATUS.md; keep both .DONE files |
100
- | `CONTEXT.md` (append-only sections) | Yes — keep both additions | Merge both additions into the relevant sections |
98
+ | Generated files (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`) | Yes — regenerate | Run package manager install command to regenerate |
99
+ | `STATUS.md` / `.DONE` files | Yes — keep both | Accept incoming STATUS.md; keep `.DONE` markers |
100
+ | `CONTEXT.md` (append-only sections) | Yes — keep both additions | Merge both additions into relevant sections |
101
101
 
102
102
  ### Auto-Resolution Rules
103
103
 
104
104
  1. **Same file, different sections:** Open the file, identify conflict markers
105
105
  (`<<<<<<<`, `=======`, `>>>>>>>`). If the conflicting hunks are in clearly
106
- different sections (different functions, different list items, different
107
- paragraphs), keep both changes. Remove all conflict markers.
106
+ different sections, keep both changes and remove markers.
108
107
 
109
- 2. **Generated files:** Do NOT manually edit. Instead:
110
- - `go.sum` Run `go mod tidy` in the affected module directory
111
- - `package-lock.json` Run `npm install` in the affected package directory
112
- - Then `git add` the regenerated file
108
+ 2. **Generated files:** Do NOT manually edit. Regenerate lockfiles using your
109
+ project's package manager command (for example `npm install`,
110
+ `pnpm install`, or `yarn install`), then `git add` the regenerated file.
113
111
 
114
- 3. **STATUS.md:** These are per-task tracking files. Accept theirs (`git checkout --theirs STATUS.md && git add STATUS.md`). Each task has its own STATUS.md so there is no meaningful merge — the incoming version is always more current.
112
+ 3. **STATUS.md:** These are per-task tracking files. Accept theirs:
113
+ ```bash
114
+ git checkout --theirs STATUS.md && git add STATUS.md
115
+ ```
115
116
 
116
- 4. **`.DONE` marker files:** These are empty sentinel files. If both sides created one, keep it (`git add .DONE`).
117
+ 4. **`.DONE` marker files:** Keep marker files if either side created one.
117
118
 
118
119
  5. **Same lines / ambiguous conflicts:** Do NOT attempt to resolve. Run
119
- `git merge --abort` and report `CONFLICT_UNRESOLVED`. The orchestrator will
120
- pause the batch for human intervention.
120
+ `git merge --abort` and report `CONFLICT_UNRESOLVED`.
121
121
 
122
122
  ---
123
123
 
@@ -130,7 +130,7 @@ Write your result as JSON to the path specified in the merge request
130
130
  {
131
131
  "status": "SUCCESS",
132
132
  "source_branch": "task/lane-1-abc123",
133
- "target_branch": "develop",
133
+ "target_branch": "main",
134
134
  "merge_commit": "abc1234def5678",
135
135
  "conflicts": [],
136
136
  "verification": {
@@ -147,42 +147,25 @@ Write your result as JSON to the path specified in the merge request
147
147
  |-------|------|-------------|
148
148
  | `status` | string | One of: `SUCCESS`, `CONFLICT_RESOLVED`, `CONFLICT_UNRESOLVED`, `BUILD_FAILURE` |
149
149
  | `source_branch` | string | The lane branch that was merged (from merge request) |
150
- | `target_branch` | string | The target branch (from merge request, typically `develop`) |
151
- | `merge_commit` | string | The merge commit SHA (only present if merge succeeded) |
150
+ | `target_branch` | string | Target branch from merge request (typically integration branch, e.g. `main`) |
151
+ | `merge_commit` | string | Merge commit SHA (present only if merge succeeded) |
152
152
  | `conflicts` | array | List of conflict entries (empty if no conflicts) |
153
- | `conflicts[].file` | string | Path to the conflicted file |
154
- | `conflicts[].type` | string | Classification: `different-sections`, `same-lines`, `generated`, `status-file` |
155
- | `conflicts[].resolved` | boolean | Whether the conflict was auto-resolved |
156
- | `conflicts[].resolution` | string | How it was resolved (e.g., `"kept both changes"`, `"regenerated"`, `"accepted theirs"`) |
153
+ | `conflicts[].file` | string | Path to conflicted file |
154
+ | `conflicts[].type` | string | Classification (`different-sections`, `same-lines`, `generated`, `status-file`) |
155
+ | `conflicts[].resolved` | boolean | Whether conflict was auto-resolved |
156
+ | `conflicts[].resolution` | string | Resolution summary |
157
157
  | `verification.ran` | boolean | Whether verification commands were executed |
158
- | `verification.passed` | boolean | Whether all verification commands passed |
159
- | `verification.output` | string | Command output (populated only on failure, truncated to 2000 chars) |
158
+ | `verification.passed` | boolean | Whether verification commands passed |
159
+ | `verification.output` | string | Verification output (useful on failures) |
160
160
 
161
161
  ### Status Definitions
162
162
 
163
163
  | Status | Meaning | Orchestrator Action |
164
164
  |--------|---------|---------------------|
165
- | `SUCCESS` | Merge completed, no conflicts, verification passed | Continue to next lane |
166
- | `CONFLICT_RESOLVED` | Conflicts occurred but were auto-resolved, verification passed | Log details, continue |
167
- | `CONFLICT_UNRESOLVED` | Conflicts that require human intervention | Pause batch, notify user |
168
- | `BUILD_FAILURE` | Merge succeeded but verification failed (merge was reverted) | Pause batch, notify user |
169
-
170
- ### Example: Clean Merge
171
-
172
- ```json
173
- {
174
- "status": "SUCCESS",
175
- "source_branch": "task/lane-1-abc123",
176
- "target_branch": "develop",
177
- "merge_commit": "abc1234def5678",
178
- "conflicts": [],
179
- "verification": {
180
- "ran": true,
181
- "passed": true,
182
- "output": ""
183
- }
184
- }
185
- ```
165
+ | `SUCCESS` | Merge completed, verification passed | Continue to next lane |
166
+ | `CONFLICT_RESOLVED` | Conflicts auto-resolved, verification passed | Log details, continue |
167
+ | `CONFLICT_UNRESOLVED` | Conflict requires human intervention | Pause batch, notify user |
168
+ | `BUILD_FAILURE` | Merge succeeded but verification failed (merge reverted) | Pause batch, notify user |
186
169
 
187
170
  ### Example: Conflict Resolved
188
171
 
@@ -190,20 +173,20 @@ Write your result as JSON to the path specified in the merge request
190
173
  {
191
174
  "status": "CONFLICT_RESOLVED",
192
175
  "source_branch": "task/lane-2-abc123",
193
- "target_branch": "develop",
176
+ "target_branch": "main",
194
177
  "merge_commit": "def4567abc8901",
195
178
  "conflicts": [
196
179
  {
197
- "file": "go.sum",
180
+ "file": "package-lock.json",
198
181
  "type": "generated",
199
182
  "resolved": true,
200
- "resolution": "regenerated via go mod tidy"
183
+ "resolution": "regenerated via npm install"
201
184
  },
202
185
  {
203
- "file": "services/time-service/internal/interfaces/http/routes/routes.go",
186
+ "file": "src/routes/api.ts",
204
187
  "type": "different-sections",
205
188
  "resolved": true,
206
- "resolution": "kept both changes — different route groups"
189
+ "resolution": "kept both route additions"
207
190
  }
208
191
  ],
209
192
  "verification": {
@@ -214,43 +197,19 @@ Write your result as JSON to the path specified in the merge request
214
197
  }
215
198
  ```
216
199
 
217
- ### Example: Unresolved Conflict
218
-
219
- ```json
220
- {
221
- "status": "CONFLICT_UNRESOLVED",
222
- "source_branch": "task/lane-3-abc123",
223
- "target_branch": "develop",
224
- "merge_commit": "",
225
- "conflicts": [
226
- {
227
- "file": "services/identity-service/internal/domain/services/auth_service.go",
228
- "type": "same-lines",
229
- "resolved": false,
230
- "resolution": ""
231
- }
232
- ],
233
- "verification": {
234
- "ran": false,
235
- "passed": false,
236
- "output": ""
237
- }
238
- }
239
- ```
240
-
241
200
  ### Example: Build Failure
242
201
 
243
202
  ```json
244
203
  {
245
204
  "status": "BUILD_FAILURE",
246
205
  "source_branch": "task/lane-1-abc123",
247
- "target_branch": "develop",
206
+ "target_branch": "main",
248
207
  "merge_commit": "",
249
208
  "conflicts": [],
250
209
  "verification": {
251
210
  "ran": true,
252
211
  "passed": false,
253
- "output": "services/time-service/internal/domain/services/pto_service.go:142:35: undefined: NewAccrualEngine"
212
+ "output": "src/server.ts:42:17 - error TS2304: Cannot find name 'createApiRouter'"
254
213
  }
255
214
  }
256
215
  ```
@@ -19,10 +19,10 @@ orchestrator:
19
19
  max_lanes: 3
20
20
 
21
21
  # Where to create worktree directories.
22
- # "sibling" = ../{prefix}-{N} (e.g. ../taskplane-wt-1)
23
- # "subdirectory" = .worktrees/{prefix}-{N} (e.g. .worktrees/taskplane-wt-1)
22
+ # "sibling" = ../{prefix}-{N} (e.g. ../project-wt-1)
23
+ # "subdirectory" = .worktrees/{prefix}-{N} (e.g. .worktrees/project-wt-1)
24
24
  worktree_location: "subdirectory"
25
- worktree_prefix: "taskplane-wt"
25
+ worktree_prefix: "project-wt"
26
26
 
27
27
  # Integration branch that lanes merge into.
28
28
  integration_branch: "main"
@@ -10,8 +10,8 @@
10
10
  # ── Project ───────────────────────────────────────────────────────────
11
11
 
12
12
  project:
13
- name: "Example Project"
14
- description: "Replace with a short description of your project"
13
+ name: "Your Project"
14
+ description: "Short description of your project"
15
15
 
16
16
  paths:
17
17
  tasks: "tasks"
@@ -21,9 +21,17 @@
21
21
  ## Mission
22
22
 
23
23
  Create a simple `hello-taskplane.md` file in the project root to verify that
24
- the Taskplane task runner is working correctly. This is a smoke test — if the
25
- worker can read this prompt, create the file, and mark the task done, the
26
- installation is healthy.
24
+ Taskplane task execution is working correctly. This is a smoke test — if the
25
+ worker can read this prompt, create the file, checkpoint progress, and mark the
26
+ task done, the installation is healthy.
27
+
28
+ ## Expected File Content
29
+
30
+ `hello-taskplane.md` should include:
31
+
32
+ - A title line (for example: `# Hello from Taskplane`)
33
+ - A line containing the task ID: `EXAMPLE-001`
34
+ - A line containing today's date
27
35
 
28
36
  ## Dependencies
29
37
 
@@ -51,12 +59,12 @@ _No additional context needed._
51
59
 
52
60
  ### Step 1: Create Hello File
53
61
 
54
- - [ ] Create `hello-taskplane.md` in the project root with a friendly message
55
- - [ ] Include the current date and the task ID (EXAMPLE-001)
62
+ - [ ] Create `hello-taskplane.md` in the project root
63
+ - [ ] Add a title plus lines containing today's date and task ID `EXAMPLE-001`
56
64
 
57
65
  ### Step 2: Verification
58
66
 
59
- - [ ] Verify `hello-taskplane.md` exists and contains the expected content
67
+ - [ ] Verify `hello-taskplane.md` exists and matches the expected content
60
68
 
61
69
  ### Step 3: Delivery
62
70
 
@@ -70,6 +78,7 @@ _No additional context needed._
70
78
  ## Completion Criteria
71
79
 
72
80
  - [ ] `hello-taskplane.md` exists in the project root
81
+ - [ ] `hello-taskplane.md` includes a title, task ID (`EXAMPLE-001`), and current date
73
82
  - [ ] `.DONE` exists in the task folder
74
83
 
75
84
  ## Git Commit Convention
@@ -22,14 +22,14 @@
22
22
  **Status:** ⬜ Not Started
23
23
 
24
24
  - [ ] Create `hello-taskplane.md` in project root
25
- - [ ] Include date and task ID
25
+ - [ ] Add title, date, and task ID (EXAMPLE-001)
26
26
 
27
27
  ---
28
28
 
29
29
  ### Step 2: Verification
30
30
  **Status:** ⬜ Not Started
31
31
 
32
- - [ ] Verify file exists with expected content
32
+ - [ ] Verify file exists and matches expected content
33
33
 
34
34
  ---
35
35
 
@@ -0,0 +1,98 @@
1
+ # Task: EXAMPLE-002 — Parallel Smoke
2
+
3
+ **Created:** {{date}}
4
+ **Size:** S
5
+
6
+ ## Review Level: 0 (None)
7
+
8
+ **Assessment:** Trivial parallel-safe smoke task to demonstrate orchestrator lanes.
9
+ **Score:** 0/8 — Blast radius: 0, Pattern novelty: 0, Security: 0, Reversibility: 0
10
+
11
+ ## Canonical Task Folder
12
+
13
+ ```
14
+ {{tasks_root}}/EXAMPLE-002-parallel-smoke/
15
+ ├── PROMPT.md ← This file (immutable above --- divider)
16
+ ├── STATUS.md ← Execution state (worker updates this)
17
+ ├── .reviews/ ← Reviewer output (task-runner creates this)
18
+ └── .DONE ← Created when complete
19
+ ```
20
+
21
+ ## Mission
22
+
23
+ Create a simple `hello-taskplane-2.md` file in the project root. This task is
24
+ intentionally independent from EXAMPLE-001 so both can run in parallel when
25
+ using `/orch`.
26
+
27
+ ## Expected File Content
28
+
29
+ `hello-taskplane-2.md` should include:
30
+
31
+ - A title line (for example: `# Parallel Hello from Taskplane`)
32
+ - A line containing the task ID: `EXAMPLE-002`
33
+ - A short note that this task is parallel-safe
34
+
35
+ ## Dependencies
36
+
37
+ - **None**
38
+
39
+ ## Context to Read First
40
+
41
+ _No additional context needed._
42
+
43
+ ## Environment
44
+
45
+ - **Workspace:** Project root
46
+ - **Services required:** None
47
+
48
+ ## File Scope
49
+
50
+ - `hello-taskplane-2.md`
51
+
52
+ ## Steps
53
+
54
+ ### Step 0: Preflight
55
+
56
+ - [ ] Verify this PROMPT.md is readable
57
+ - [ ] Verify STATUS.md exists in the same folder
58
+
59
+ ### Step 1: Create Parallel Hello File
60
+
61
+ - [ ] Create `hello-taskplane-2.md` in the project root
62
+ - [ ] Add title plus lines containing task ID `EXAMPLE-002` and a parallel-safe note
63
+
64
+ ### Step 2: Verification
65
+
66
+ - [ ] Verify `hello-taskplane-2.md` exists and matches the expected content
67
+
68
+ ### Step 3: Delivery
69
+
70
+ - [ ] `.DONE` created in this folder
71
+
72
+ ## Documentation Requirements
73
+
74
+ **Must Update:** None
75
+ **Check If Affected:** None
76
+
77
+ ## Completion Criteria
78
+
79
+ - [ ] `hello-taskplane-2.md` exists in the project root
80
+ - [ ] `hello-taskplane-2.md` includes a title, task ID (`EXAMPLE-002`), and a parallel-safe note
81
+ - [ ] `.DONE` exists in the task folder
82
+
83
+ ## Git Commit Convention
84
+
85
+ - **Implementation:** `feat(EXAMPLE-002): description`
86
+ - **Checkpoints:** `checkpoint: EXAMPLE-002 description`
87
+
88
+ ## Do NOT
89
+
90
+ - Modify any existing project files
91
+ - Create files outside the project root
92
+ - Add dependencies between EXAMPLE-001 and EXAMPLE-002
93
+
94
+ ---
95
+
96
+ ## Amendments (Added During Execution)
97
+
98
+ <!-- Workers add amendments here if issues discovered during execution. -->
@@ -0,0 +1,73 @@
1
+ # EXAMPLE-002: Parallel Smoke — Status
2
+
3
+ **Current Step:** Not Started
4
+ **Status:** 🔵 Ready for Execution
5
+ **Last Updated:** {{date}}
6
+ **Review Level:** 0
7
+ **Review Counter:** 0
8
+ **Iteration:** 0
9
+ **Size:** S
10
+
11
+ ---
12
+
13
+ ### Step 0: Preflight
14
+ **Status:** ⬜ Not Started
15
+
16
+ - [ ] Verify PROMPT.md is readable
17
+ - [ ] Verify STATUS.md exists
18
+
19
+ ---
20
+
21
+ ### Step 1: Create Parallel Hello File
22
+ **Status:** ⬜ Not Started
23
+
24
+ - [ ] Create `hello-taskplane-2.md` in project root
25
+ - [ ] Add title, task ID (EXAMPLE-002), and parallel-safe note
26
+
27
+ ---
28
+
29
+ ### Step 2: Verification
30
+ **Status:** ⬜ Not Started
31
+
32
+ - [ ] Verify file exists and matches expected content
33
+
34
+ ---
35
+
36
+ ### Step 3: Delivery
37
+ **Status:** ⬜ Not Started
38
+
39
+ - [ ] `.DONE` created
40
+
41
+ ---
42
+
43
+ ## Reviews
44
+
45
+ | # | Type | Step | Verdict | File |
46
+ |---|------|------|---------|------|
47
+
48
+ ---
49
+
50
+ ## Discoveries
51
+
52
+ | Discovery | Disposition | Location |
53
+ |-----------|-------------|----------|
54
+
55
+ ---
56
+
57
+ ## Execution Log
58
+
59
+ | Timestamp | Action | Outcome |
60
+ |-----------|--------|---------|
61
+ | {{date}} | Task staged | PROMPT.md and STATUS.md created |
62
+
63
+ ---
64
+
65
+ ## Blockers
66
+
67
+ *None*
68
+
69
+ ---
70
+
71
+ ## Notes
72
+
73
+ *This is an example task created by `taskplane init` to demonstrate orchestrator-first onboarding.*