taskplane 0.1.6 → 0.1.7

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
  /**
@@ -725,7 +726,7 @@ export async function executeLane(
725
726
  };
726
727
  }
727
728
 
728
-
729
+
729
730
  // ── STATUS.md Parsing for Worktree ───────────────────────────────────
730
731
 
731
732
  /**
@@ -873,7 +874,7 @@ export function parseWorktreeStatusMd(
873
874
  };
874
875
  }
875
876
 
876
-
877
+
877
878
  // ── State Resolution ─────────────────────────────────────────────────
878
879
 
879
880
  /**
@@ -1063,7 +1064,7 @@ export function resolveTaskMonitorState(
1063
1064
  };
1064
1065
  }
1065
1066
 
1066
-
1067
+
1067
1068
  // ── Core Monitor Loop ────────────────────────────────────────────────
1068
1069
 
1069
1070
  /**
@@ -1326,7 +1327,7 @@ export async function monitorLanes(
1326
1327
  };
1327
1328
  }
1328
1329
 
1329
-
1330
+
1330
1331
  // ── Transitive Dependent Computation ─────────────────────────────────
1331
1332
 
1332
1333
  /**
@@ -1370,7 +1371,101 @@ export function computeTransitiveDependents(
1370
1371
  return blocked;
1371
1372
  }
1372
1373
 
1373
-
1374
+
1375
+ // ── Pre-flight: Commit Untracked Task Files ─────────────────────────
1376
+
1377
+ /**
1378
+ * Ensure all task files for a wave are committed to git before worktree creation.
1379
+ *
1380
+ * Git worktrees only contain tracked (committed) files. If a user creates
1381
+ * task folders (PROMPT.md, STATUS.md) but doesn't commit them, the worktree
1382
+ * won't have those files and TASK_AUTOSTART will fail with "file not found".
1383
+ *
1384
+ * This function checks each wave task's folder for untracked or modified files,
1385
+ * stages them, and creates a commit on the current branch. This must run BEFORE
1386
+ * allocateLanes() so that worktrees (which are based on the integration branch)
1387
+ * include the task files.
1388
+ *
1389
+ * Only task-specific folders are staged — no other working tree changes are touched.
1390
+ *
1391
+ * @param waveTasks - Task IDs in this wave
1392
+ * @param pending - Full pending task map from discovery
1393
+ * @param repoRoot - Main repository root
1394
+ * @param waveIndex - Wave number for commit message
1395
+ */
1396
+ export function ensureTaskFilesCommitted(
1397
+ waveTasks: string[],
1398
+ pending: Map<string, ParsedTask>,
1399
+ repoRoot: string,
1400
+ waveIndex: number,
1401
+ ): void {
1402
+ // Collect task folder paths for this wave
1403
+ const foldersToCheck: { taskId: string; relPath: string }[] = [];
1404
+ for (const taskId of waveTasks) {
1405
+ const task = pending.get(taskId);
1406
+ if (!task) continue;
1407
+
1408
+ const absFolder = resolve(task.taskFolder);
1409
+ const relPath = relative(resolve(repoRoot), absFolder).replace(/\\/g, "/");
1410
+
1411
+ // Skip if path escapes the repo (shouldn't happen in normal use)
1412
+ if (relPath.startsWith("..")) {
1413
+ continue;
1414
+ }
1415
+ foldersToCheck.push({ taskId, relPath });
1416
+ }
1417
+
1418
+ if (foldersToCheck.length === 0) return;
1419
+
1420
+ // Check which folders have untracked or uncommitted files
1421
+ const foldersToStage: string[] = [];
1422
+ for (const { taskId, relPath } of foldersToCheck) {
1423
+ const status = runGit(["status", "--porcelain", "--", relPath], repoRoot);
1424
+ if (status.ok && status.stdout.trim()) {
1425
+ execLog("wave", `W${waveIndex}`, `task ${taskId} has uncommitted files, staging`, {
1426
+ folder: relPath,
1427
+ status: status.stdout.trim().split("\n").slice(0, 5).join("; "),
1428
+ });
1429
+ foldersToStage.push(relPath);
1430
+ }
1431
+ }
1432
+
1433
+ if (foldersToStage.length === 0) return;
1434
+
1435
+ // Stage only the task folders
1436
+ for (const folder of foldersToStage) {
1437
+ const addResult = runGit(["add", "--", folder], repoRoot);
1438
+ if (!addResult.ok) {
1439
+ execLog("wave", `W${waveIndex}`, `failed to stage task files: ${addResult.stderr}`, { folder });
1440
+ throw new ExecutionError(
1441
+ "EXEC_TASK_STAGE_FAILED",
1442
+ `Failed to stage task files in "${folder}": ${addResult.stderr}`,
1443
+ "wave",
1444
+ folder,
1445
+ );
1446
+ }
1447
+ }
1448
+
1449
+ // Commit
1450
+ const taskIds = foldersToStage.map(f => f.split("/").pop() || f).join(", ");
1451
+ const commitMsg = `chore: stage task files for orchestrator wave ${waveIndex} (${taskIds})`;
1452
+ const commitResult = runGit(["commit", "-m", commitMsg], repoRoot);
1453
+ if (!commitResult.ok) {
1454
+ execLog("wave", `W${waveIndex}`, `failed to commit task files: ${commitResult.stderr}`);
1455
+ throw new ExecutionError(
1456
+ "EXEC_TASK_COMMIT_FAILED",
1457
+ `Failed to commit task files for wave ${waveIndex}: ${commitResult.stderr}`,
1458
+ "wave",
1459
+ `W${waveIndex}`,
1460
+ );
1461
+ }
1462
+
1463
+ execLog("wave", `W${waveIndex}`, `committed ${foldersToStage.length} task folder(s) to ensure worktree visibility`, {
1464
+ folders: foldersToStage,
1465
+ commit: commitResult.stdout.trim().split("\n")[0],
1466
+ });
1467
+ }
1468
+
1374
1469
  // ── Wave Execution Core ──────────────────────────────────────────────
1375
1470
 
1376
1471
  /**
@@ -1432,6 +1527,34 @@ export async function executeWave(
1432
1527
  batchId,
1433
1528
  });
1434
1529
 
1530
+ // ── Stage 0: Ensure task files are committed ────────────────
1531
+ // Task folders may contain untracked files (PROMPT.md, STATUS.md) that
1532
+ // won't appear in worktrees unless committed. Stage and commit them now,
1533
+ // before worktree creation, so workers can find their TASK_AUTOSTART paths.
1534
+ try {
1535
+ ensureTaskFilesCommitted(waveTasks, pending, repoRoot, waveIndex);
1536
+ } catch (err: unknown) {
1537
+ const errMsg = err instanceof Error ? err.message : String(err);
1538
+ execLog("wave", `W${waveIndex}`, `task file commit failed: ${errMsg}`);
1539
+
1540
+ return {
1541
+ waveIndex,
1542
+ startedAt,
1543
+ endedAt: Date.now(),
1544
+ laneResults: [],
1545
+ policyApplied: policy,
1546
+ stoppedEarly: true,
1547
+ failedTaskIds: waveTasks,
1548
+ skippedTaskIds: [],
1549
+ succeededTaskIds: [],
1550
+ blockedTaskIds: [...computeTransitiveDependents(new Set(waveTasks), dependencyGraph)],
1551
+ laneCount: 0,
1552
+ overallStatus: "failed",
1553
+ finalMonitorState: null,
1554
+ allocatedLanes: [],
1555
+ };
1556
+ }
1557
+
1435
1558
  // ── Stage 1: Allocate lanes ──────────────────────────────────
1436
1559
  const allocResult = allocateLanes(waveTasks, pending, config, repoRoot, batchId);
1437
1560
 
@@ -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.7",
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
+ }