taskplane 0.5.4 → 0.5.5

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.
@@ -2,8 +2,8 @@
2
2
  * Main batch execution engine
3
3
  * @module orch/engine
4
4
  */
5
- import { readFileSync, readdirSync, unlinkSync } from "fs";
6
- import { join } from "path";
5
+ import { existsSync, readFileSync, readdirSync, unlinkSync } from "fs";
6
+ import { dirname, join, resolve } from "path";
7
7
 
8
8
  import { formatDiscoveryResults, runDiscovery } from "./discovery.ts";
9
9
  import { execLog, executeWave, tmuxKillSession } from "./execution.ts";
@@ -339,6 +339,16 @@ export async function executeOrchBatch(
339
339
  }
340
340
  }
341
341
 
342
+ // ── Workspace mode: commit task artifacts to task-area repos ─
343
+ // In workspace mode, workers write .DONE and STATUS.md to the
344
+ // canonical task folder (e.g., shared-libs/task-management/...) via
345
+ // absolute paths, not to the lane worktree. These changes land as
346
+ // uncommitted modifications in the task-area repo's working tree.
347
+ // Commit them before the merge step so they appear in the orch branch.
348
+ if (workspaceConfig && waveResult.succeededTaskIds.length > 0) {
349
+ commitWorkspaceTaskArtifacts(discoveryRef, workspaceRoot ?? repoRoot, waveIdx + 1, batchState.batchId);
350
+ }
351
+
342
352
  // ── Wave Merge ───────────────────────────────────────────
343
353
  // Only merge if there are succeeded tasks in this wave
344
354
  let mergeResult: MergeWaveResult | null = null;
@@ -847,5 +857,78 @@ export async function executeOrchBatch(
847
857
  }
848
858
 
849
859
 
860
+ // ── Workspace Task Artifact Commit ───────────────────────────────────
861
+
862
+ /**
863
+ * In workspace mode, commit task artifacts (.DONE, STATUS.md) that workers
864
+ * wrote to the canonical task folder in the task-area repo.
865
+ *
866
+ * Workers write to absolute paths (e.g., shared-libs/task-management/.../TP-002/.DONE)
867
+ * which land as uncommitted changes in the task-area repo's working tree.
868
+ * This function finds all task-area repos with dirty task files and commits them
869
+ * so they appear in the lane branches and merge correctly.
870
+ *
871
+ * Best-effort: failures are logged but don't block the batch.
872
+ */
873
+ function commitWorkspaceTaskArtifacts(
874
+ discovery: DiscoveryResult | null,
875
+ workspaceRoot: string,
876
+ waveIndex: number,
877
+ batchId: string,
878
+ ): void {
879
+ if (!discovery) return;
880
+
881
+ // Collect unique repo roots that contain task folders
882
+ const repoRootsWithTasks = new Set<string>();
883
+ for (const [, task] of discovery.pending) {
884
+ const taskFolder = resolve(task.taskFolder);
885
+ // Walk up to find the git repo root for this task folder
886
+ const gitResult = runGit(["rev-parse", "--show-toplevel"], dirname(taskFolder));
887
+ if (gitResult.ok) {
888
+ repoRootsWithTasks.add(gitResult.stdout.trim().replace(/\\/g, "/"));
889
+ }
890
+ }
891
+
892
+ for (const taskRepoRoot of repoRootsWithTasks) {
893
+ // Check for uncommitted changes
894
+ const statusResult = runGit(["status", "--porcelain", "--", "task-management/"], taskRepoRoot);
895
+ if (!statusResult.ok) {
896
+ // Try without path filter (task area might have different name)
897
+ const statusAll = runGit(["status", "--porcelain"], taskRepoRoot);
898
+ if (!statusAll.ok || !statusAll.stdout.trim()) continue;
899
+ }
900
+ if (statusResult.ok && !statusResult.stdout.trim()) continue;
901
+
902
+ // Stage task artifacts (only .DONE and STATUS.md files)
903
+ const lines = (statusResult.stdout || "").split("\n").filter(l => l.trim());
904
+ let hasTaskArtifacts = false;
905
+ for (const line of lines) {
906
+ const file = line.slice(3).trim();
907
+ if (file.endsWith(".DONE") || file.endsWith("STATUS.md")) {
908
+ const addResult = runGit(["add", file], taskRepoRoot);
909
+ if (addResult.ok) hasTaskArtifacts = true;
910
+ }
911
+ }
912
+
913
+ if (!hasTaskArtifacts) continue;
914
+
915
+ // Commit
916
+ const commitResult = runGit(
917
+ ["commit", "-m", `checkpoint: wave ${waveIndex} task artifacts (.DONE, STATUS.md)`],
918
+ taskRepoRoot,
919
+ );
920
+ if (commitResult.ok) {
921
+ execLog("batch", batchId, `committed workspace task artifacts`, {
922
+ repoRoot: taskRepoRoot,
923
+ wave: waveIndex,
924
+ });
925
+ } else if (!commitResult.stderr.includes("nothing to commit")) {
926
+ execLog("batch", batchId, `workspace task artifact commit failed (non-fatal): ${commitResult.stderr.slice(0, 200)}`, {
927
+ repoRoot: taskRepoRoot,
928
+ });
929
+ }
930
+ }
931
+ }
932
+
850
933
  // ── Dashboard Widget (Step 6) ────────────────────────────────────────
851
934
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",