taskplane 0.5.0 → 0.5.2
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.
|
@@ -817,18 +817,30 @@ export async function executeOrchBatch(
|
|
|
817
817
|
batchState.skippedTasks,
|
|
818
818
|
batchState.blockedTasks,
|
|
819
819
|
totalElapsedSec,
|
|
820
|
+
batchState.orchBranch,
|
|
821
|
+
batchState.baseBranch,
|
|
820
822
|
),
|
|
821
823
|
batchState.failedTasks > 0 ? "warning" : "info",
|
|
822
824
|
);
|
|
823
825
|
|
|
824
|
-
// ──
|
|
826
|
+
// ── Preserve state for /orch-integrate when orch branch exists ──
|
|
827
|
+
// If integration is "manual" and we have an orch branch, keep the
|
|
828
|
+
// state file so /orch-integrate can find orchBranch and baseBranch.
|
|
829
|
+
// Only delete state if there's no orch branch to integrate.
|
|
825
830
|
if (batchState.phase === "completed") {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
831
|
+
if (batchState.orchBranch) {
|
|
832
|
+
execLog("state", batchState.batchId, "state file preserved for /orch-integrate", {
|
|
833
|
+
orchBranch: batchState.orchBranch,
|
|
834
|
+
});
|
|
835
|
+
} else {
|
|
836
|
+
// Legacy mode (no orch branch) — clean up state
|
|
837
|
+
try {
|
|
838
|
+
deleteBatchState(stateRoot);
|
|
839
|
+
execLog("state", batchState.batchId, "state file deleted on clean completion");
|
|
840
|
+
} catch (err: unknown) {
|
|
841
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
842
|
+
execLog("state", batchState.batchId, `failed to delete state file: ${msg}`);
|
|
843
|
+
}
|
|
832
844
|
}
|
|
833
845
|
}
|
|
834
846
|
}
|
|
@@ -11,6 +11,53 @@ import type { AllocatedLane, AllocatedTask, DependencyGraph, LaneExecutionResult
|
|
|
11
11
|
import { allocateLanes } from "./waves.ts";
|
|
12
12
|
import { runGit } from "./git.ts";
|
|
13
13
|
|
|
14
|
+
// ── Task Runner Extension Path Resolution ────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find the task-runner extension path for lane sessions.
|
|
18
|
+
*
|
|
19
|
+
* Resolution order:
|
|
20
|
+
* 1. Local project: {repoRoot}/extensions/task-runner.ts (for taskplane dev)
|
|
21
|
+
* 2. Global npm (Windows): {APPDATA}/npm/node_modules/taskplane/extensions/task-runner.ts
|
|
22
|
+
* 3. Global npm (Unix): /usr/local/lib/node_modules/taskplane/extensions/task-runner.ts
|
|
23
|
+
* 4. npm peer: resolve from pi's location
|
|
24
|
+
*
|
|
25
|
+
* @throws ExecutionError if task-runner.ts cannot be found anywhere
|
|
26
|
+
*/
|
|
27
|
+
function resolveTaskRunnerExtensionPath(repoRoot: string): string {
|
|
28
|
+
const extFile = join("extensions", "task-runner.ts");
|
|
29
|
+
|
|
30
|
+
// 1. Local project (taskplane development)
|
|
31
|
+
const localPath = join(resolve(repoRoot), extFile);
|
|
32
|
+
if (existsSync(localPath)) return localPath;
|
|
33
|
+
|
|
34
|
+
// 2. Global npm install paths
|
|
35
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
36
|
+
const candidates: string[] = [];
|
|
37
|
+
if (process.env.APPDATA) {
|
|
38
|
+
candidates.push(join(process.env.APPDATA, "npm", "node_modules", "taskplane", extFile));
|
|
39
|
+
}
|
|
40
|
+
if (home) {
|
|
41
|
+
candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", "taskplane", extFile));
|
|
42
|
+
candidates.push(join(home, ".npm-global", "lib", "node_modules", "taskplane", extFile));
|
|
43
|
+
}
|
|
44
|
+
candidates.push(join("/usr", "local", "lib", "node_modules", "taskplane", extFile));
|
|
45
|
+
|
|
46
|
+
// 3. Peer of pi's package
|
|
47
|
+
try {
|
|
48
|
+
const piPath = process.argv[1] || "";
|
|
49
|
+
const piPkgDir = resolve(piPath, "..", "..");
|
|
50
|
+
candidates.push(join(piPkgDir, "..", "taskplane", extFile));
|
|
51
|
+
} catch { /* ignore */ }
|
|
52
|
+
|
|
53
|
+
for (const candidate of candidates) {
|
|
54
|
+
if (existsSync(candidate)) return candidate;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback: return the local path (will fail at spawn time with a clear error)
|
|
58
|
+
return localPath;
|
|
59
|
+
}
|
|
60
|
+
|
|
14
61
|
// ── Execution Helpers ────────────────────────────────────────────────
|
|
15
62
|
|
|
16
63
|
/**
|
|
@@ -142,9 +189,11 @@ export function buildLaneEnvVars(
|
|
|
142
189
|
};
|
|
143
190
|
|
|
144
191
|
// In workspace mode, the worktree cwd is inside a repo — not the workspace root.
|
|
145
|
-
// The task-runner needs TASKPLANE_WORKSPACE_ROOT to find .pi/
|
|
192
|
+
// The task-runner needs TASKPLANE_WORKSPACE_ROOT to find .pi/ config
|
|
146
193
|
// and resolve task area paths from the correct base directory.
|
|
147
|
-
|
|
194
|
+
// Always set when workspaceRoot is provided (workspace mode), regardless of
|
|
195
|
+
// whether it equals repoRoot (it often does — cwd is the workspace root).
|
|
196
|
+
if (workspaceRoot) {
|
|
148
197
|
vars.TASKPLANE_WORKSPACE_ROOT = workspaceRoot;
|
|
149
198
|
}
|
|
150
199
|
|
|
@@ -206,7 +255,7 @@ export function buildTmuxSpawnArgs(
|
|
|
206
255
|
.map(([key, val]) => `${key}=${shellQuote(val)}`)
|
|
207
256
|
.join(" ");
|
|
208
257
|
|
|
209
|
-
const taskRunnerExtPath =
|
|
258
|
+
const taskRunnerExtPath = resolveTaskRunnerExtensionPath(repoRoot);
|
|
210
259
|
const basePiCommand = `${envParts} pi --no-session -e ${shellQuote(taskRunnerExtPath)}`;
|
|
211
260
|
|
|
212
261
|
// NOTE: Do not redirect lane output here. Shell redirection has proven
|
|
@@ -36,7 +36,7 @@ export const ORCH_MESSAGES = {
|
|
|
36
36
|
`🔀 [Wave ${waveNum}] Merge: placeholder — Step 3 (TS-008) will replace with mergeWave()`,
|
|
37
37
|
orchWorktreeReset: (waveNum: number, lanes: number) =>
|
|
38
38
|
`🔄 Resetting ${lanes} worktree(s) to target branch HEAD after wave ${waveNum}`,
|
|
39
|
-
orchBatchComplete: (batchId: string, succeeded: number, failed: number, skipped: number, blocked: number, elapsedSec: number) => {
|
|
39
|
+
orchBatchComplete: (batchId: string, succeeded: number, failed: number, skipped: number, blocked: number, elapsedSec: number, orchBranch?: string, baseBranch?: string) => {
|
|
40
40
|
const lines = [`\n🏁 Batch ${batchId} complete: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped, ${blocked} blocked (${elapsedSec}s)`];
|
|
41
41
|
if (failed > 0 || blocked > 0) {
|
|
42
42
|
lines.push("");
|
|
@@ -48,6 +48,17 @@ export const ORCH_MESSAGES = {
|
|
|
48
48
|
lines.push(" • /orch-resume — retry from the failed wave");
|
|
49
49
|
lines.push(" • /orch-abort — clean up and start fresh");
|
|
50
50
|
}
|
|
51
|
+
if (orchBranch && succeeded > 0) {
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push(` ℹ Orch branch: ${orchBranch}`);
|
|
54
|
+
if (baseBranch) {
|
|
55
|
+
lines.push(` Review changes: git log ${baseBranch}..${orchBranch}`);
|
|
56
|
+
}
|
|
57
|
+
lines.push(" Next steps:");
|
|
58
|
+
lines.push(" • /orch-integrate — fast-forward into your branch");
|
|
59
|
+
lines.push(" • /orch-integrate --merge — merge (if branches diverged)");
|
|
60
|
+
lines.push(" • /orch-integrate --pr — push and open a PR");
|
|
61
|
+
}
|
|
51
62
|
return lines.join("\n");
|
|
52
63
|
},
|
|
53
64
|
orchBatchFailed: (batchId: string, reason: string) =>
|