taskplane 0.1.6 → 0.1.8

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.
@@ -1,15 +1,16 @@
1
- /**
2
- * Lane execution, monitoring, wave execution loop
3
- * @module orch/execution
4
- */
5
- import { readFileSync, existsSync, statSync, unlinkSync, mkdirSync } from "fs";
6
- import { spawnSync } from "child_process";
7
- import { join, dirname, resolve, delimiter as pathDelimiter } from "path";
8
-
9
- import { DONE_GRACE_MS, EXECUTION_POLL_INTERVAL_MS, ExecutionError, SESSION_SPAWN_RETRY_MAX } from "./types.ts";
10
- import type { AllocatedLane, AllocatedTask, DependencyGraph, LaneExecutionResult, LaneMonitorSnapshot, LaneTaskOutcome, LaneTaskStatus, MonitorState, MtimeTracker, OrchestratorConfig, ParsedTask, TaskMonitorSnapshot, WaveExecutionResult } from "./types.ts";
11
- import { allocateLanes } from "./waves.ts";
12
-
1
+ /**
2
+ * Lane execution, monitoring, wave execution loop
3
+ * @module orch/execution
4
+ */
5
+ import { readFileSync, existsSync, statSync, unlinkSync, mkdirSync } from "fs";
6
+ import { spawnSync } from "child_process";
7
+ import { join, dirname, resolve, relative, delimiter as pathDelimiter } from "path";
8
+
9
+ import { DONE_GRACE_MS, EXECUTION_POLL_INTERVAL_MS, ExecutionError, SESSION_SPAWN_RETRY_MAX } from "./types.ts";
10
+ import type { AllocatedLane, AllocatedTask, DependencyGraph, LaneExecutionResult, LaneMonitorSnapshot, LaneTaskOutcome, LaneTaskStatus, MonitorState, MtimeTracker, OrchestratorConfig, ParsedTask, TaskMonitorSnapshot, WaveExecutionResult } from "./types.ts";
11
+ import { allocateLanes } from "./waves.ts";
12
+ import { runGit } from "./git.ts";
13
+
13
14
  // ── Execution Helpers ────────────────────────────────────────────────
14
15
 
15
16
  /**
@@ -479,6 +480,12 @@ export async function pollUntilTaskComplete(
479
480
 
480
481
  let lastPaneTail = "";
481
482
 
483
+ // Abort signal file path — checked each poll cycle.
484
+ // Any process can create this file to trigger abort (belt-and-suspenders
485
+ // alongside the in-memory pauseSignal, since /orch-abort may not be able
486
+ // to run concurrently with the /orch command handler).
487
+ const abortSignalFile = join(repoRoot, ".pi", "orch-abort-signal");
488
+
482
489
  // Main polling loop
483
490
  while (true) {
484
491
  // Check pause signal
@@ -493,6 +500,20 @@ export async function pollUntilTaskComplete(
493
500
  };
494
501
  }
495
502
 
503
+ // Check file-based abort signal
504
+ if (existsSync(abortSignalFile)) {
505
+ execLog(laneId, task.taskId, "abort signal file detected — killing session and aborting");
506
+ tmuxKillSession(sessionName);
507
+ // Also kill child sessions (worker, reviewer)
508
+ tmuxKillSession(`${sessionName}-worker`);
509
+ tmuxKillSession(`${sessionName}-reviewer`);
510
+ return {
511
+ status: "failed",
512
+ exitReason: "Aborted by signal file (.pi/orch-abort-signal)",
513
+ doneFileFound: false,
514
+ };
515
+ }
516
+
496
517
  // Capture live pane output for diagnostics (best effort).
497
518
  const paneTail = captureTmuxPaneTail(sessionName);
498
519
  if (paneTail) {
@@ -725,7 +746,7 @@ export async function executeLane(
725
746
  };
726
747
  }
727
748
 
728
-
749
+
729
750
  // ── STATUS.md Parsing for Worktree ───────────────────────────────────
730
751
 
731
752
  /**
@@ -873,7 +894,7 @@ export function parseWorktreeStatusMd(
873
894
  };
874
895
  }
875
896
 
876
-
897
+
877
898
  // ── State Resolution ─────────────────────────────────────────────────
878
899
 
879
900
  /**
@@ -1063,7 +1084,7 @@ export function resolveTaskMonitorState(
1063
1084
  };
1064
1085
  }
1065
1086
 
1066
-
1087
+
1067
1088
  // ── Core Monitor Loop ────────────────────────────────────────────────
1068
1089
 
1069
1090
  /**
@@ -1326,7 +1347,7 @@ export async function monitorLanes(
1326
1347
  };
1327
1348
  }
1328
1349
 
1329
-
1350
+
1330
1351
  // ── Transitive Dependent Computation ─────────────────────────────────
1331
1352
 
1332
1353
  /**
@@ -1370,7 +1391,101 @@ export function computeTransitiveDependents(
1370
1391
  return blocked;
1371
1392
  }
1372
1393
 
1373
-
1394
+
1395
+ // ── Pre-flight: Commit Untracked Task Files ─────────────────────────
1396
+
1397
+ /**
1398
+ * Ensure all task files for a wave are committed to git before worktree creation.
1399
+ *
1400
+ * Git worktrees only contain tracked (committed) files. If a user creates
1401
+ * task folders (PROMPT.md, STATUS.md) but doesn't commit them, the worktree
1402
+ * won't have those files and TASK_AUTOSTART will fail with "file not found".
1403
+ *
1404
+ * This function checks each wave task's folder for untracked or modified files,
1405
+ * stages them, and creates a commit on the current branch. This must run BEFORE
1406
+ * allocateLanes() so that worktrees (which are based on the integration branch)
1407
+ * include the task files.
1408
+ *
1409
+ * Only task-specific folders are staged — no other working tree changes are touched.
1410
+ *
1411
+ * @param waveTasks - Task IDs in this wave
1412
+ * @param pending - Full pending task map from discovery
1413
+ * @param repoRoot - Main repository root
1414
+ * @param waveIndex - Wave number for commit message
1415
+ */
1416
+ export function ensureTaskFilesCommitted(
1417
+ waveTasks: string[],
1418
+ pending: Map<string, ParsedTask>,
1419
+ repoRoot: string,
1420
+ waveIndex: number,
1421
+ ): void {
1422
+ // Collect task folder paths for this wave
1423
+ const foldersToCheck: { taskId: string; relPath: string }[] = [];
1424
+ for (const taskId of waveTasks) {
1425
+ const task = pending.get(taskId);
1426
+ if (!task) continue;
1427
+
1428
+ const absFolder = resolve(task.taskFolder);
1429
+ const relPath = relative(resolve(repoRoot), absFolder).replace(/\\/g, "/");
1430
+
1431
+ // Skip if path escapes the repo (shouldn't happen in normal use)
1432
+ if (relPath.startsWith("..")) {
1433
+ continue;
1434
+ }
1435
+ foldersToCheck.push({ taskId, relPath });
1436
+ }
1437
+
1438
+ if (foldersToCheck.length === 0) return;
1439
+
1440
+ // Check which folders have untracked or uncommitted files
1441
+ const foldersToStage: string[] = [];
1442
+ for (const { taskId, relPath } of foldersToCheck) {
1443
+ const status = runGit(["status", "--porcelain", "--", relPath], repoRoot);
1444
+ if (status.ok && status.stdout.trim()) {
1445
+ execLog("wave", `W${waveIndex}`, `task ${taskId} has uncommitted files, staging`, {
1446
+ folder: relPath,
1447
+ status: status.stdout.trim().split("\n").slice(0, 5).join("; "),
1448
+ });
1449
+ foldersToStage.push(relPath);
1450
+ }
1451
+ }
1452
+
1453
+ if (foldersToStage.length === 0) return;
1454
+
1455
+ // Stage only the task folders
1456
+ for (const folder of foldersToStage) {
1457
+ const addResult = runGit(["add", "--", folder], repoRoot);
1458
+ if (!addResult.ok) {
1459
+ execLog("wave", `W${waveIndex}`, `failed to stage task files: ${addResult.stderr}`, { folder });
1460
+ throw new ExecutionError(
1461
+ "EXEC_TASK_STAGE_FAILED",
1462
+ `Failed to stage task files in "${folder}": ${addResult.stderr}`,
1463
+ "wave",
1464
+ folder,
1465
+ );
1466
+ }
1467
+ }
1468
+
1469
+ // Commit
1470
+ const taskIds = foldersToStage.map(f => f.split("/").pop() || f).join(", ");
1471
+ const commitMsg = `chore: stage task files for orchestrator wave ${waveIndex} (${taskIds})`;
1472
+ const commitResult = runGit(["commit", "-m", commitMsg], repoRoot);
1473
+ if (!commitResult.ok) {
1474
+ execLog("wave", `W${waveIndex}`, `failed to commit task files: ${commitResult.stderr}`);
1475
+ throw new ExecutionError(
1476
+ "EXEC_TASK_COMMIT_FAILED",
1477
+ `Failed to commit task files for wave ${waveIndex}: ${commitResult.stderr}`,
1478
+ "wave",
1479
+ `W${waveIndex}`,
1480
+ );
1481
+ }
1482
+
1483
+ execLog("wave", `W${waveIndex}`, `committed ${foldersToStage.length} task folder(s) to ensure worktree visibility`, {
1484
+ folders: foldersToStage,
1485
+ commit: commitResult.stdout.trim().split("\n")[0],
1486
+ });
1487
+ }
1488
+
1374
1489
  // ── Wave Execution Core ──────────────────────────────────────────────
1375
1490
 
1376
1491
  /**
@@ -1432,6 +1547,34 @@ export async function executeWave(
1432
1547
  batchId,
1433
1548
  });
1434
1549
 
1550
+ // ── Stage 0: Ensure task files are committed ────────────────
1551
+ // Task folders may contain untracked files (PROMPT.md, STATUS.md) that
1552
+ // won't appear in worktrees unless committed. Stage and commit them now,
1553
+ // before worktree creation, so workers can find their TASK_AUTOSTART paths.
1554
+ try {
1555
+ ensureTaskFilesCommitted(waveTasks, pending, repoRoot, waveIndex);
1556
+ } catch (err: unknown) {
1557
+ const errMsg = err instanceof Error ? err.message : String(err);
1558
+ execLog("wave", `W${waveIndex}`, `task file commit failed: ${errMsg}`);
1559
+
1560
+ return {
1561
+ waveIndex,
1562
+ startedAt,
1563
+ endedAt: Date.now(),
1564
+ laneResults: [],
1565
+ policyApplied: policy,
1566
+ stoppedEarly: true,
1567
+ failedTaskIds: waveTasks,
1568
+ skippedTaskIds: [],
1569
+ succeededTaskIds: [],
1570
+ blockedTaskIds: [...computeTransitiveDependents(new Set(waveTasks), dependencyGraph)],
1571
+ laneCount: 0,
1572
+ overallStatus: "failed",
1573
+ finalMonitorState: null,
1574
+ allocatedLanes: [],
1575
+ };
1576
+ }
1577
+
1435
1578
  // ── Stage 1: Allocate lanes ──────────────────────────────────
1436
1579
  const allocResult = allocateLanes(waveTasks, pending, config, repoRoot, batchId);
1437
1580
 
@@ -1,6 +1,8 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
 
3
3
  import { execSync } from "child_process";
4
+ import { writeFileSync, unlinkSync, mkdirSync } from "fs";
5
+ import { join } from "path";
4
6
 
5
7
  import {
6
8
  DEFAULT_ORCHESTRATOR_CONFIG,
@@ -10,7 +12,6 @@ import {
10
12
  createOrchWidget,
11
13
  deleteBatchState,
12
14
  detectOrphanSessions,
13
- executeAbort,
14
15
  executeLane,
15
16
  executeOrchBatch,
16
17
  formatDependencyGraph,
@@ -347,113 +348,144 @@ export default function (pi: ExtensionAPI) {
347
348
  pi.registerCommand("orch-abort", {
348
349
  description: "Abort batch: /orch-abort [--hard]",
349
350
  handler: async (args, ctx) => {
350
- const hard = args?.trim() === "--hard";
351
- const mode: AbortMode = hard ? "hard" : "graceful";
352
- const prefix = orchConfig.orchestrator.tmux_prefix;
353
- const gracePeriodMs = orchConfig.orchestrator.abort_grace_period * 1000;
354
-
355
- // Check for active in-memory batch
356
- const hasActiveBatch = orchBatchState.phase !== "idle" &&
357
- orchBatchState.phase !== "completed" &&
358
- orchBatchState.phase !== "failed" &&
359
- orchBatchState.phase !== "stopped";
360
-
361
- // Also check for persisted state (abort can work on orphaned batches too)
362
- let persistedState: PersistedBatchState | null = null;
363
351
  try {
364
- persistedState = loadBatchState(ctx.cwd);
365
- } catch {
366
- // Ignore we may still have in-memory state or orphan sessions
367
- }
352
+ const hard = args?.trim() === "--hard";
353
+ const mode: AbortMode = hard ? "hard" : "graceful";
354
+ const prefix = orchConfig.orchestrator.tmux_prefix;
355
+ const gracePeriodMs = orchConfig.orchestrator.abort_grace_period * 1000;
356
+
357
+ ctx.ui.notify(`🛑 Abort requested (${mode} mode, prefix: ${prefix})...`, "info");
358
+
359
+ // ── Step 1: Write abort signal file immediately ──────────
360
+ // This is the primary abort mechanism. The orchestrator's polling
361
+ // loop checks for this file on every cycle, so even if this command
362
+ // handler runs concurrently with /orch (or is queued behind it),
363
+ // the signal file will be detected.
364
+ const abortSignalFile = join(ctx.cwd, ".pi", "orch-abort-signal");
365
+ try {
366
+ mkdirSync(join(ctx.cwd, ".pi"), { recursive: true });
367
+ writeFileSync(abortSignalFile, `abort requested at ${new Date().toISOString()} (mode: ${mode})`, "utf-8");
368
+ ctx.ui.notify(" ✓ Abort signal file written (.pi/orch-abort-signal)", "info");
369
+ } catch (err) {
370
+ ctx.ui.notify(` ⚠ Failed to write abort signal file: ${err instanceof Error ? err.message : String(err)}`, "warning");
371
+ }
368
372
 
369
- // If no in-memory batch AND no persisted state, check for orphan sessions
370
- if (!hasActiveBatch && !persistedState) {
371
- // Last chance: check for orphan sessions
372
- const sessionNames = parseOrchSessionNames(
373
- (() => {
374
- try {
375
- return execSync('tmux list-sessions -F "#{session_name}"', {
376
- encoding: "utf-8",
377
- timeout: 5000,
378
- });
379
- } catch {
380
- return "";
381
- }
382
- })(),
383
- prefix,
384
- );
385
- if (sessionNames.length === 0) {
386
- ctx.ui.notify(ORCH_MESSAGES.abortNoBatch(), "warning");
387
- return;
373
+ // ── Step 2: Set pause signal immediately ─────────────────
374
+ // Belt-and-suspenders: if the /orch polling loop can see this
375
+ // shared object, it will stop on the next iteration.
376
+ if (orchBatchState.pauseSignal) {
377
+ orchBatchState.pauseSignal.paused = true;
378
+ ctx.ui.notify(" ✓ Pause signal set on in-memory batch state", "info");
388
379
  }
389
- // If orphan sessions exist, proceed with abort (will kill them)
390
- }
391
380
 
392
- const batchId = orchBatchState.batchId || persistedState?.batchId || "unknown";
381
+ // ── Step 3: Check what we're aborting ────────────────────
382
+ const hasActiveBatch = orchBatchState.phase !== "idle" &&
383
+ orchBatchState.phase !== "completed" &&
384
+ orchBatchState.phase !== "failed" &&
385
+ orchBatchState.phase !== "stopped";
386
+
387
+ let persistedState: PersistedBatchState | null = null;
388
+ try {
389
+ persistedState = loadBatchState(ctx.cwd);
390
+ } catch {
391
+ // Ignore — we may still have in-memory state or orphan sessions
392
+ }
393
393
 
394
- // Notify user of abort start
395
- if (mode === "graceful") {
396
- const sessionCount = orchBatchState.currentLanes.length || persistedState?.tasks.length || 0;
397
- ctx.ui.notify(ORCH_MESSAGES.abortGracefulStarting(batchId, sessionCount), "info");
398
394
  ctx.ui.notify(
399
- ORCH_MESSAGES.abortGracefulWaiting(batchId, orchConfig.orchestrator.abort_grace_period),
395
+ ` Batch state: in-memory=${hasActiveBatch ? orchBatchState.phase : "none"}, ` +
396
+ `persisted=${persistedState ? persistedState.batchId : "none"}`,
400
397
  "info",
401
398
  );
402
- } else {
403
- const sessionCount = orchBatchState.currentLanes.length || persistedState?.tasks.length || 0;
404
- ctx.ui.notify(ORCH_MESSAGES.abortHardStarting(batchId, sessionCount), "info");
405
- }
406
399
 
407
- // Execute abort
408
- const result = await executeAbort(
409
- mode,
410
- prefix,
411
- ctx.cwd,
412
- orchBatchState,
413
- persistedState,
414
- gracePeriodMs,
415
- );
400
+ // ── Step 4: Scan for tmux sessions ──────────────────────
401
+ let allSessionNames: string[] = [];
402
+ try {
403
+ const tmuxOutput = execSync('tmux list-sessions -F "#{session_name}"', {
404
+ encoding: "utf-8",
405
+ timeout: 5000,
406
+ }).trim();
407
+ const all = tmuxOutput ? tmuxOutput.split("\n").map(s => s.trim()).filter(Boolean) : [];
408
+ allSessionNames = all.filter(name => name.startsWith(`${prefix}-`));
409
+ ctx.ui.notify(` Found ${allSessionNames.length} session(s) matching prefix "${prefix}-": ${allSessionNames.join(", ") || "(none)"}`, "info");
410
+ } catch {
411
+ ctx.ui.notify(" ⚠ Could not list tmux sessions (tmux not available?)", "warning");
412
+ }
416
413
 
417
- // Update in-memory batch state
418
- orchBatchState.phase = "stopped";
419
- orchBatchState.endedAt = result.durationMs + Date.now() - result.durationMs; // Use actual time
420
- updateOrchWidget();
414
+ // If no batch AND no sessions, nothing to abort
415
+ if (!hasActiveBatch && !persistedState && allSessionNames.length === 0) {
416
+ ctx.ui.notify(ORCH_MESSAGES.abortNoBatch(), "warning");
417
+ // Clean up signal file
418
+ try { unlinkSync(abortSignalFile); } catch {}
419
+ return;
420
+ }
421
+
422
+ const batchId = orchBatchState.batchId || persistedState?.batchId || "unknown";
421
423
 
422
- // Notify results
423
- const durationSec = Math.round(result.durationMs / 1000);
424
- if (mode === "graceful") {
425
- const forceKilled = result.sessionsKilled - result.gracefulExits;
426
- if (forceKilled > 0) {
427
- ctx.ui.notify(
428
- ORCH_MESSAGES.abortGracefulForceKill(forceKilled),
429
- "warning",
430
- );
424
+ // ── Step 5: Kill sessions directly (fast path) ──────────
425
+ // For hard mode or when sessions are found, kill them immediately
426
+ // rather than waiting through the full executeAbort flow.
427
+ if (allSessionNames.length > 0) {
428
+ ctx.ui.notify(` Killing ${allSessionNames.length} tmux session(s)...`, "info");
429
+ let killed = 0;
430
+ for (const name of allSessionNames) {
431
+ try {
432
+ // Kill child sessions first (worker, reviewer)
433
+ execSync(`tmux kill-session -t "${name}-worker" 2>/dev/null`, { timeout: 3000 }).toString();
434
+ } catch {}
435
+ try {
436
+ execSync(`tmux kill-session -t "${name}-reviewer" 2>/dev/null`, { timeout: 3000 }).toString();
437
+ } catch {}
438
+ try {
439
+ execSync(`tmux kill-session -t "${name}" 2>/dev/null`, { timeout: 3000 }).toString();
440
+ killed++;
441
+ ctx.ui.notify(` ✓ Killed: ${name}`, "info");
442
+ } catch {
443
+ // Session may have already exited
444
+ ctx.ui.notify(` · ${name} (already exited)`, "info");
445
+ killed++;
446
+ }
447
+ }
448
+ ctx.ui.notify(` ✓ ${killed}/${allSessionNames.length} session(s) terminated`, "info");
449
+ } else {
450
+ ctx.ui.notify(" No tmux sessions to kill", "info");
431
451
  }
452
+
453
+ // ── Step 6: Clean up batch state ────────────────────────
454
+ try {
455
+ orchBatchState.phase = "stopped";
456
+ orchBatchState.endedAt = Date.now();
457
+ updateOrchWidget();
458
+ ctx.ui.notify(" ✓ In-memory batch state set to 'stopped'", "info");
459
+ } catch (err) {
460
+ ctx.ui.notify(` ⚠ Failed to update in-memory state: ${err instanceof Error ? err.message : String(err)}`, "warning");
461
+ }
462
+
463
+ try {
464
+ deleteBatchState(ctx.cwd);
465
+ ctx.ui.notify(" ✓ Batch state file deleted (.pi/batch-state.json)", "info");
466
+ } catch (err) {
467
+ ctx.ui.notify(` ⚠ Failed to delete batch state file: ${err instanceof Error ? err.message : String(err)}`, "warning");
468
+ }
469
+
470
+ // ── Step 7: Clean up abort signal file ───────────────────
471
+ try { unlinkSync(abortSignalFile); } catch {}
472
+
473
+ // ── Done ─────────────────────────────────────────────────
432
474
  ctx.ui.notify(
433
- ORCH_MESSAGES.abortGracefulComplete(batchId, result.gracefulExits, forceKilled, durationSec),
434
- "info",
435
- );
436
- } else {
437
- ctx.ui.notify(
438
- ORCH_MESSAGES.abortHardComplete(batchId, result.sessionsKilled, durationSec),
475
+ `✅ Abort complete for batch ${batchId}. Sessions killed, state cleaned up.\n` +
476
+ ` Worktrees and branches are preserved for inspection.`,
439
477
  "info",
440
478
  );
441
- }
442
-
443
- // Report errors if any
444
- if (result.errors.length > 0) {
445
- const errorDetails = result.errors.map(e => ` • [${e.code}] ${e.message}`).join("\n");
479
+ } catch (err) {
480
+ // Top-level catch: ensure the user ALWAYS sees something
446
481
  ctx.ui.notify(
447
- `${ORCH_MESSAGES.abortPartialFailure(result.errors.length)}\n${errorDetails}`,
448
- "warning",
482
+ `❌ Abort failed with error: ${err instanceof Error ? err.message : String(err)}\n` +
483
+ ` Stack: ${err instanceof Error ? err.stack : "N/A"}\n\n` +
484
+ ` Manual cleanup: tmux kill-server (kills ALL tmux sessions)\n` +
485
+ ` Or: tmux kill-session -t <session-name> for each session`,
486
+ "error",
449
487
  );
450
488
  }
451
-
452
- // Final message
453
- ctx.ui.notify(
454
- ORCH_MESSAGES.abortComplete(mode, result.sessionsKilled),
455
- "info",
456
- );
457
489
  },
458
490
  });
459
491
 
@@ -559,6 +559,8 @@ export const SESSION_SPAWN_RETRY_MAX = 2;
559
559
  * - EXEC_SPAWN_FAILED: TMUX session could not be created after retries
560
560
  * - EXEC_TASK_FAILED: task completed without .DONE (non-zero exit)
561
561
  * - EXEC_TASK_STALLED: STATUS.md unchanged for stall_timeout (handled by Step 3)
562
+ * - EXEC_TASK_STAGE_FAILED: git add failed for task files
563
+ * - EXEC_TASK_COMMIT_FAILED: git commit failed for staged task files
562
564
  * - EXEC_TMUX_NOT_AVAILABLE: tmux binary not found
563
565
  * - EXEC_WORKTREE_MISSING: lane worktree path doesn't exist
564
566
  */
@@ -566,6 +568,8 @@ export type ExecutionErrorCode =
566
568
  | "EXEC_SPAWN_FAILED"
567
569
  | "EXEC_TASK_FAILED"
568
570
  | "EXEC_TASK_STALLED"
571
+ | "EXEC_TASK_STAGE_FAILED"
572
+ | "EXEC_TASK_COMMIT_FAILED"
569
573
  | "EXEC_TMUX_NOT_AVAILABLE"
570
574
  | "EXEC_WORKTREE_MISSING";
571
575
 
package/package.json CHANGED
@@ -1,57 +1,57 @@
1
- {
2
- "name": "taskplane",
3
- "version": "0.1.6",
4
- "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
- "keywords": [
6
- "pi-package",
7
- "ai",
8
- "agent",
9
- "orchestration",
10
- "task-runner",
11
- "parallel"
12
- ],
13
- "bin": {
14
- "taskplane": "bin/taskplane.mjs"
15
- },
16
- "pi": {
17
- "extensions": [
18
- "./extensions/task-runner.ts",
19
- "./extensions/task-orchestrator.ts"
20
- ],
21
- "skills": [
22
- "./skills"
23
- ]
24
- },
25
- "type": "module",
26
- "engines": {
27
- "node": ">=20.0.0"
28
- },
29
- "files": [
30
- "bin/",
31
- "dashboard/",
32
- "extensions/task-runner.ts",
33
- "extensions/task-orchestrator.ts",
34
- "extensions/taskplane/",
35
- "skills/",
36
- "templates/"
37
- ],
38
- "peerDependencies": {
39
- "@mariozechner/pi-coding-agent": "*",
40
- "@mariozechner/pi-tui": "*",
41
- "@mariozechner/pi-ai": "*",
42
- "@sinclair/typebox": "*"
43
- },
44
- "dependencies": {
45
- "yaml": "^2.4.0"
46
- },
47
- "license": "MIT",
48
- "repository": {
49
- "type": "git",
50
- "url": "git+https://github.com/HenryLach/taskplane.git"
51
- },
52
- "homepage": "https://github.com/HenryLach/taskplane#readme",
53
- "bugs": {
54
- "url": "https://github.com/HenryLach/taskplane/issues"
55
- },
56
- "author": "Henry Lach"
57
- }
1
+ {
2
+ "name": "taskplane",
3
+ "version": "0.1.8",
4
+ "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
+ "keywords": [
6
+ "pi-package",
7
+ "ai",
8
+ "agent",
9
+ "orchestration",
10
+ "task-runner",
11
+ "parallel"
12
+ ],
13
+ "bin": {
14
+ "taskplane": "bin/taskplane.mjs"
15
+ },
16
+ "pi": {
17
+ "extensions": [
18
+ "./extensions/task-runner.ts",
19
+ "./extensions/task-orchestrator.ts"
20
+ ],
21
+ "skills": [
22
+ "./skills"
23
+ ]
24
+ },
25
+ "type": "module",
26
+ "engines": {
27
+ "node": ">=20.0.0"
28
+ },
29
+ "files": [
30
+ "bin/",
31
+ "dashboard/",
32
+ "extensions/task-runner.ts",
33
+ "extensions/task-orchestrator.ts",
34
+ "extensions/taskplane/",
35
+ "skills/",
36
+ "templates/"
37
+ ],
38
+ "peerDependencies": {
39
+ "@mariozechner/pi-coding-agent": "*",
40
+ "@mariozechner/pi-tui": "*",
41
+ "@mariozechner/pi-ai": "*",
42
+ "@sinclair/typebox": "*"
43
+ },
44
+ "dependencies": {
45
+ "yaml": "^2.4.0"
46
+ },
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/HenryLach/taskplane.git"
51
+ },
52
+ "homepage": "https://github.com/HenryLach/taskplane#readme",
53
+ "bugs": {
54
+ "url": "https://github.com/HenryLach/taskplane/issues"
55
+ },
56
+ "author": "Henry Lach"
57
+ }