taskplane 0.2.9 → 0.3.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,7 +19,24 @@ Taskplane turns your coding project into an AI-managed task board. You define ta
19
19
 
20
20
  ## Install
21
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.
22
+ 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.
23
+
24
+ ### Prerequisites
25
+
26
+ | Dependency | Required | Notes |
27
+ |-----------|----------|-------|
28
+ | [Node.js](https://nodejs.org/) ≥ 22 | Yes | Runtime |
29
+ | [pi](https://github.com/badlogic/pi-mono) | Yes | Agent framework |
30
+ | [Git](https://git-scm.com/) | Yes | Version control, worktrees |
31
+ | **tmux** | **Strongly recommended** | Required for `/orch` parallel execution |
32
+
33
+ **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:
34
+
35
+ ```bash
36
+ taskplane install-tmux
37
+ ```
38
+
39
+ On macOS: `brew install tmux` · On Linux: `sudo apt install tmux` (or your distro's package manager)
23
40
 
24
41
  ### Option A: Global Install (all projects)
25
42
 
@@ -138,6 +155,7 @@ Orchestrator lanes execute tasks through task-runner under the hood, so `/task`
138
155
  |---------|-------------|
139
156
  | `taskplane init` | Scaffold project config (interactive or `--preset`) |
140
157
  | `taskplane doctor` | Validate installation and config |
158
+ | `taskplane install-tmux` | Install or upgrade tmux for Git Bash (Windows) |
141
159
  | `taskplane version` | Show version info |
142
160
  | `taskplane dashboard` | Launch the web dashboard |
143
161
  | `taskplane uninstall` | Remove Taskplane project files and optionally uninstall package (`--package`) |
package/bin/taskplane.mjs CHANGED
@@ -10,6 +10,18 @@
10
10
  * and auto-discovered by pi. This CLI is for everything else.
11
11
  */
12
12
 
13
+ // ─── Node.js version gate (fail fast) ───────────────────────────────────────
14
+
15
+ const MIN_NODE_MAJOR = 22;
16
+ const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
17
+ if (nodeMajor < MIN_NODE_MAJOR) {
18
+ console.error(
19
+ `\x1b[31m❌ Taskplane requires Node.js >= ${MIN_NODE_MAJOR}.0.0 (found ${process.versions.node}).\x1b[0m\n` +
20
+ ` Upgrade: https://nodejs.org/\n`
21
+ );
22
+ process.exit(1);
23
+ }
24
+
13
25
  import fs from "node:fs";
14
26
  import path from "node:path";
15
27
  import readline from "node:readline";
@@ -633,10 +645,11 @@ async function cmdInit(args) {
633
645
  console.log(`\n${c.bold}Creating files...${c.reset}\n`);
634
646
  const skipIfExists = !force;
635
647
 
636
- // Agent prompts
648
+ // Agent prompts — copy thin local files (base prompts ship in the package
649
+ // and are composed automatically by the task-runner at runtime)
637
650
  for (const agent of ["task-worker.md", "task-reviewer.md", "task-merger.md"]) {
638
651
  copyTemplate(
639
- path.join(TEMPLATES_DIR, "agents", agent),
652
+ path.join(TEMPLATES_DIR, "agents", "local", agent),
640
653
  path.join(projectRoot, ".pi", "agents", agent),
641
654
  { skipIfExists, label: `.pi/agents/${agent}` }
642
655
  );
@@ -1249,10 +1262,10 @@ function cmdDoctor() {
1249
1262
  const checks = [
1250
1263
  { label: "pi installed", check: () => commandExists("pi"), detail: () => getVersion("pi") },
1251
1264
  {
1252
- label: "Node.js >= 20.0.0",
1265
+ label: "Node.js >= 22.0.0",
1253
1266
  check: () => {
1254
1267
  const v = process.versions.node;
1255
- return parseInt(v.split(".")[0]) >= 20;
1268
+ return parseInt(v.split(".")[0]) >= 22;
1256
1269
  },
1257
1270
  detail: () => `v${process.versions.node}`,
1258
1271
  },
@@ -339,21 +339,141 @@ function clearConversationLog(prefix: string): void {
339
339
 
340
340
  // ── Agent Loader ─────────────────────────────────────────────────────
341
341
 
342
+ /**
343
+ * Parse a markdown agent file into frontmatter key-value pairs and body content.
344
+ * Returns null if the file doesn't exist or has no frontmatter block.
345
+ */
346
+ function parseAgentFile(filePath: string): { fm: Record<string, string>; body: string } | null {
347
+ if (!existsSync(filePath)) return null;
348
+ const raw = readFileSync(filePath, "utf-8").replace(/\r\n/g, "\n");
349
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
350
+ if (!match) return null;
351
+ const fm: Record<string, string> = {};
352
+ for (const line of match[1].split("\n")) {
353
+ const idx = line.indexOf(":");
354
+ if (idx > 0) fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
355
+ }
356
+ return { fm, body: match[2].trim() };
357
+ }
358
+
359
+ /** Cached package root — resolved once, reused for all agent file lookups. */
360
+ let _packageRoot: string | null = null;
361
+
362
+ /**
363
+ * Find the taskplane package root directory.
364
+ *
365
+ * Strategy: this file lives at <package-root>/extensions/task-runner.ts.
366
+ * When pi loads it via `-e`, it resolves the full path. We can find the
367
+ * package root by searching for package.json with name "taskplane"
368
+ * starting from known candidate locations.
369
+ */
370
+ function findPackageRoot(): string {
371
+ if (_packageRoot !== null) return _packageRoot;
372
+
373
+ // Strategy 1: Walk up from this file's location via require.resolve or npm paths
374
+ const candidates: string[] = [];
375
+
376
+ // The extension is loaded by pi from the installed package location.
377
+ // Check well-known npm global paths.
378
+ const home = process.env.HOME || process.env.USERPROFILE || "";
379
+ if (home) {
380
+ candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane"));
381
+ candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane"));
382
+ }
383
+ candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane"));
384
+
385
+ // Strategy 2: resolve from pi's node_modules peer
386
+ try {
387
+ const piPath = process.argv[1] || "";
388
+ const piPkgDir = resolve(piPath, "..", "..");
389
+ candidates.push(join(piPkgDir, "..", "taskplane"));
390
+ } catch { /* ignore */ }
391
+
392
+ // Strategy 3: Check TASKPLANE_WORKSPACE_ROOT project-local install
393
+ const wsRoot = process.env.TASKPLANE_WORKSPACE_ROOT;
394
+ if (wsRoot) {
395
+ candidates.push(join(wsRoot, ".pi", "npm", "node_modules", "taskplane"));
396
+ candidates.push(join(wsRoot, "node_modules", "taskplane"));
397
+ }
398
+
399
+ for (const dir of candidates) {
400
+ try {
401
+ const pkgPath = join(dir, "package.json");
402
+ if (existsSync(pkgPath)) {
403
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
404
+ if (pkg.name === "taskplane") {
405
+ _packageRoot = dir;
406
+ return dir;
407
+ }
408
+ }
409
+ } catch { /* ignore */ }
410
+ }
411
+
412
+ _packageRoot = "";
413
+ return "";
414
+ }
415
+
416
+ /**
417
+ * Resolve the package-shipped base agent file path.
418
+ * Base files live in the package's templates/agents/ directory.
419
+ */
420
+ function resolveBaseAgentPath(name: string): string {
421
+ const root = findPackageRoot();
422
+ if (!root) return "";
423
+ return join(root, "templates", "agents", `${name}.md`);
424
+ }
425
+
426
+ /**
427
+ * Load an agent definition with prompt inheritance.
428
+ *
429
+ * Inheritance model (default: compose base + local):
430
+ * 1. Load base agent from the shipped package (templates/agents/{name}.md)
431
+ * 2. Load local agent from .pi/agents/{name}.md (if it exists)
432
+ * 3. If local file has `standalone: true` in frontmatter, use it as-is (no base)
433
+ * 4. Otherwise, compose: base prompt + separator + local content
434
+ * 5. Local frontmatter values (tools, model) override base values
435
+ *
436
+ * If no local file exists, the base file is used directly.
437
+ * If no base file exists (e.g., custom agent), local file is used as-is.
438
+ */
342
439
  function loadAgentDef(cwd: string, name: string): { systemPrompt: string; tools: string; model: string } | null {
343
- const paths = [join(cwd, ".pi", "agents", `${name}.md`), join(cwd, "agents", `${name}.md`)];
344
- for (const p of paths) {
345
- if (!existsSync(p)) continue;
346
- const raw = readFileSync(p, "utf-8").replace(/\r\n/g, "\n");
347
- const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
348
- if (!match) continue;
349
- const fm: Record<string, string> = {};
350
- for (const line of match[1].split("\n")) {
351
- const idx = line.indexOf(":");
352
- if (idx > 0) fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
353
- }
354
- return { systemPrompt: match[2].trim(), tools: fm.tools || "read,grep,find,ls", model: fm.model || "" };
440
+ const basePath = resolveBaseAgentPath(name);
441
+ const localPaths = [join(cwd, ".pi", "agents", `${name}.md`), join(cwd, "agents", `${name}.md`)];
442
+
443
+ // Load base from package
444
+ const baseDef = parseAgentFile(basePath);
445
+
446
+ // Load local override (first found wins)
447
+ let localDef: { fm: Record<string, string>; body: string } | null = null;
448
+ for (const p of localPaths) {
449
+ localDef = parseAgentFile(p);
450
+ if (localDef) break;
451
+ }
452
+
453
+ // No base and no local → null
454
+ if (!baseDef && !localDef) return null;
455
+
456
+ // Local with standalone: true → use local as-is, ignore base
457
+ if (localDef?.fm.standalone === "true") {
458
+ return {
459
+ systemPrompt: localDef.body,
460
+ tools: localDef.fm.tools || "read,grep,find,ls",
461
+ model: localDef.fm.model || "",
462
+ };
355
463
  }
356
- return null;
464
+
465
+ // Compose base + local
466
+ const basePrompt = baseDef?.body || "";
467
+ const localPrompt = localDef?.body || "";
468
+ const composedPrompt = localPrompt
469
+ ? basePrompt + "\n\n---\n\n## Project-Specific Guidance\n\n" + localPrompt
470
+ : basePrompt;
471
+
472
+ // Local frontmatter overrides base (tools, model)
473
+ const tools = localDef?.fm.tools || baseDef?.fm.tools || "read,grep,find,ls";
474
+ const model = localDef?.fm.model || baseDef?.fm.model || "";
475
+
476
+ return { systemPrompt: composedPrompt.trim(), tools, model };
357
477
  }
358
478
 
359
479
  // ── PROMPT.md Parser ─────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "type": "module",
26
26
  "engines": {
27
- "node": ">=20.0.0"
27
+ "node": ">=22.0.0"
28
28
  },
29
29
  "files": [
30
30
  "bin/",
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: task-merger
3
+ # tools: read,write,edit,bash,grep,find,ls
4
+ # model:
5
+ # standalone: true
6
+ ---
7
+
8
+ <!-- ═══════════════════════════════════════════════════════════════════
9
+ Project-Specific Merger Guidance
10
+
11
+ This file is COMPOSED with the base task-merger prompt shipped in the
12
+ taskplane package. Your content here is appended after the base prompt.
13
+
14
+ The base prompt (maintained by taskplane) handles:
15
+ - Branch merge workflow (fast-forward, 3-way, conflict resolution)
16
+ - Post-merge verification command execution
17
+ - Result file JSON format and writing conventions
18
+
19
+ Add project-specific merge rules below. Common examples:
20
+ - Post-merge verification commands (build, lint, test)
21
+ - Conflict resolution preferences
22
+ - Protected files that should never be auto-merged
23
+
24
+ To override frontmatter values (tools, model), uncomment and edit above.
25
+ To use this file as a FULLY STANDALONE prompt (ignoring the base),
26
+ uncomment `standalone: true` above and write the complete prompt below.
27
+ ═══════════════════════════════════════════════════════════════════ -->
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: task-reviewer
3
+ # tools: read,write,bash,grep,find,ls
4
+ # model: openai/gpt-5.3-codex
5
+ # standalone: true
6
+ ---
7
+
8
+ <!-- ═══════════════════════════════════════════════════════════════════
9
+ Project-Specific Reviewer Guidance
10
+
11
+ This file is COMPOSED with the base task-reviewer prompt shipped in the
12
+ taskplane package. Your content here is appended after the base prompt.
13
+
14
+ The base prompt (maintained by taskplane) handles:
15
+ - Plan review and code review workflows
16
+ - Verdict format (APPROVE / REVISE)
17
+ - Review file output conventions
18
+ - Plan granularity guidance
19
+
20
+ Add project-specific review criteria below. Common examples:
21
+ - Required test coverage thresholds
22
+ - Security review checklist items
23
+ - Architecture constraints to enforce
24
+ - Performance requirements
25
+
26
+ To override frontmatter values (tools, model), uncomment and edit above.
27
+ To use this file as a FULLY STANDALONE prompt (ignoring the base),
28
+ uncomment `standalone: true` above and write the complete prompt below.
29
+ ═══════════════════════════════════════════════════════════════════ -->
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: task-worker
3
+ # tools: read,write,edit,bash,grep,find,ls
4
+ # model: anthropic/claude-sonnet-4-20250514
5
+ # standalone: true
6
+ ---
7
+
8
+ <!-- ═══════════════════════════════════════════════════════════════════
9
+ Project-Specific Worker Guidance
10
+
11
+ This file is COMPOSED with the base task-worker prompt shipped in the
12
+ taskplane package. Your content here is appended after the base prompt.
13
+
14
+ The base prompt (maintained by taskplane) handles:
15
+ - STATUS.md-first workflow and checkpoint discipline
16
+ - Fresh-context loop behavior and iteration rules
17
+ - Git commit conventions and .DONE file creation
18
+ - Review response handling
19
+
20
+ Add project-specific rules below. Common examples:
21
+ - Preferred package manager (pnpm, yarn, bun)
22
+ - Test commands (make test, npm run test:unit)
23
+ - Coding standards (linting, formatting)
24
+ - Framework-specific patterns
25
+ - Environment or deployment constraints
26
+
27
+ To override frontmatter values (tools, model), uncomment and edit above.
28
+ To use this file as a FULLY STANDALONE prompt (ignoring the base),
29
+ uncomment `standalone: true` above and write the complete prompt below.
30
+ ═══════════════════════════════════════════════════════════════════ -->