taskplane 0.5.0 → 0.5.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.
@@ -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
- // ── TS-009: Delete state file on clean completion (no failures) ──
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
- try {
827
- deleteBatchState(stateRoot);
828
- execLog("state", batchState.batchId, "state file deleted on clean completion");
829
- } catch (err: unknown) {
830
- const msg = err instanceof Error ? err.message : String(err);
831
- execLog("state", batchState.batchId, `failed to delete state file: ${msg}`);
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
  /**
@@ -206,7 +253,7 @@ export function buildTmuxSpawnArgs(
206
253
  .map(([key, val]) => `${key}=${shellQuote(val)}`)
207
254
  .join(" ");
208
255
 
209
- const taskRunnerExtPath = join(resolve(repoRoot), "extensions", "task-runner.ts");
256
+ const taskRunnerExtPath = resolveTaskRunnerExtensionPath(repoRoot);
210
257
  const basePiCommand = `${envParts} pi --no-session -e ${shellQuote(taskRunnerExtPath)}`;
211
258
 
212
259
  // 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) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",