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.
- package/dashboard/public/app.js +1144 -1144
- package/dashboard/public/index.html +98 -98
- package/dashboard/public/style.css +1108 -1108
- package/dashboard/server.cjs +638 -638
- package/extensions/taskplane/execution.ts +140 -17
- package/extensions/taskplane/types.ts +4 -0
- package/package.json +57 -57
|
@@ -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.
|
|
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
|
+
}
|