taskplane 0.22.12 → 0.22.14

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,7 +2,7 @@
2
2
  * Merge orchestration, merge agents, merge worktree
3
3
  * @module orch/merge
4
4
  */
5
- import { readFileSync, writeFileSync, existsSync, unlinkSync, copyFileSync, mkdirSync, rmSync } from "fs";
5
+ import { readFileSync, writeFileSync, existsSync, unlinkSync, copyFileSync, mkdirSync, rmSync, readdirSync } from "fs";
6
6
  import { readFile as fsReadFile } from "fs/promises";
7
7
  import { execSync, spawnSync } from "child_process";
8
8
  import { join, dirname, resolve, relative } from "path";
@@ -1841,16 +1841,48 @@ export async function mergeWave(
1841
1841
  // ── Stage workspace task artifacts into merge worktree ──────────
1842
1842
  // TP-035: Tightened artifact staging — only allowlisted task-owned files
1843
1843
  // are staged. The allowlist is derived per-task-folder from completed lanes:
1844
- // exactly `.DONE`, `STATUS.md`, and `REVIEW_VERDICT.json` (when present).
1844
+ // `.DONE`, `STATUS.md`, `REVIEW_VERDICT.json`, and `.reviews/**` files.
1845
1845
  // Files outside known task folders, worktree internals, and repo-escape
1846
1846
  // paths are rejected. Uses resolve+relative path containment consistent
1847
1847
  // with ensureTaskFilesCommitted() in execution.ts.
1848
1848
  if (mergeWorkDir) {
1849
1849
  // Build the set of allowed artifact paths (repo-root-relative) from
1850
1850
  // the completed lanes' task folders.
1851
+ //
1852
+ // Allowlist policy:
1853
+ // - task marker files: .DONE, STATUS.md, REVIEW_VERDICT.json
1854
+ // - review outputs under task-local .reviews/**
1851
1855
  const ALLOWED_ARTIFACT_NAMES = [".DONE", "STATUS.md", "REVIEW_VERDICT.json"];
1856
+ const ALLOWED_ARTIFACT_DIRS = [".reviews"];
1852
1857
  const resolvedRepoRoot = resolve(repoRoot);
1853
1858
  const allowedRelPaths = new Set<string>();
1859
+ const relPathToWorktree = new Map<string, string>();
1860
+
1861
+ const listFilesRecursively = (rootDir: string): string[] => {
1862
+ if (!existsSync(rootDir)) return [];
1863
+ const files: string[] = [];
1864
+ const walk = (dir: string): void => {
1865
+ let entries;
1866
+ try {
1867
+ entries = readdirSync(dir, { withFileTypes: true });
1868
+ } catch {
1869
+ return;
1870
+ }
1871
+ for (const entry of entries) {
1872
+ const absPath = join(dir, entry.name);
1873
+ if (entry.isDirectory()) {
1874
+ walk(absPath);
1875
+ continue;
1876
+ }
1877
+ if (!entry.isFile()) continue;
1878
+ const relPath = relative(rootDir, absPath).replace(/\\/g, "/");
1879
+ if (!relPath || relPath.startsWith("..") || relPath.startsWith("/")) continue;
1880
+ files.push(relPath);
1881
+ }
1882
+ };
1883
+ walk(rootDir);
1884
+ return files;
1885
+ };
1854
1886
 
1855
1887
  for (const lane of orderedLanes) {
1856
1888
  for (const allocTask of lane.tasks) {
@@ -1867,7 +1899,24 @@ export async function mergeWave(
1867
1899
  }
1868
1900
 
1869
1901
  for (const name of ALLOWED_ARTIFACT_NAMES) {
1870
- allowedRelPaths.add(`${relFolder}/${name}`);
1902
+ const rp = `${relFolder}/${name}`;
1903
+ allowedRelPaths.add(rp);
1904
+ relPathToWorktree.set(rp, join(lane.worktreePath, rp));
1905
+ }
1906
+
1907
+ for (const dirName of ALLOWED_ARTIFACT_DIRS) {
1908
+ const laneDir = join(lane.worktreePath, relFolder, dirName);
1909
+ for (const relFile of listFilesRecursively(laneDir)) {
1910
+ const rp = `${relFolder}/${dirName}/${relFile}`;
1911
+ allowedRelPaths.add(rp);
1912
+ relPathToWorktree.set(rp, join(lane.worktreePath, rp));
1913
+ }
1914
+
1915
+ const repoDir = join(repoRoot, relFolder, dirName);
1916
+ for (const relFile of listFilesRecursively(repoDir)) {
1917
+ const rp = `${relFolder}/${dirName}/${relFile}`;
1918
+ allowedRelPaths.add(rp);
1919
+ }
1871
1920
  }
1872
1921
  }
1873
1922
  }
@@ -1875,12 +1924,44 @@ export async function mergeWave(
1875
1924
  if (allowedRelPaths.size > 0) {
1876
1925
  let staged = 0;
1877
1926
  let skipped = 0;
1927
+ let preserved = 0;
1878
1928
 
1879
1929
  for (const relPath of allowedRelPaths) {
1880
- const srcPath = join(repoRoot, relPath);
1881
- if (!existsSync(srcPath)) continue; // File not present (e.g., no REVIEW_VERDICT.json) — skip silently
1882
-
1883
1930
  const destPath = join(mergeWorkDir, relPath);
1931
+
1932
+ // TP-099: If the file already exists in mergeWorkDir (from lane merge),
1933
+ // do NOT overwrite it — the lane merge brought the correct worker-updated
1934
+ // version (e.g., STATUS.md with checked items, execution log, discoveries).
1935
+ // Overwriting from repoRoot would revert to the pre-execution template.
1936
+ if (existsSync(destPath)) {
1937
+ preserved++;
1938
+ continue;
1939
+ }
1940
+
1941
+ // File missing from mergeWorkDir — backfill from best available source.
1942
+ // Primary: lane worktree (has worker-generated .DONE/STATUS/.reviews content).
1943
+ // Fallback: repoRoot (original task folder, with path containment check).
1944
+ const worktreeSrc = relPathToWorktree.get(relPath);
1945
+ let srcPath: string | null = null;
1946
+
1947
+ // Try lane worktree first (trusted engine-allocated path)
1948
+ if (worktreeSrc && existsSync(worktreeSrc)) {
1949
+ srcPath = worktreeSrc;
1950
+ } else {
1951
+ // Fallback to repoRoot with path containment check (TP-035 hardening)
1952
+ const repoRootSrc = join(repoRoot, relPath);
1953
+ if (existsSync(repoRootSrc)) {
1954
+ const resolvedSrc = resolve(repoRootSrc);
1955
+ const srcRelToRepo = relative(resolvedRepoRoot, resolvedSrc).replace(/\\/g, "/");
1956
+ if (srcRelToRepo.startsWith("..") || srcRelToRepo.startsWith("/")) {
1957
+ execLog("merge", `W${waveIndex}`, `skipping artifact source outside repo root`, { path: relPath, src: repoRootSrc });
1958
+ continue;
1959
+ }
1960
+ srcPath = repoRootSrc;
1961
+ }
1962
+ }
1963
+ if (!srcPath) continue; // File not present anywhere — skip silently
1964
+
1884
1965
  try {
1885
1966
  mkdirSync(dirname(destPath), { recursive: true });
1886
1967
  copyFileSync(srcPath, destPath);
@@ -1894,13 +1975,14 @@ export async function mergeWave(
1894
1975
  }
1895
1976
 
1896
1977
  if (staged > 0) {
1897
- spawnSync("git", ["commit", "-m", `checkpoint: wave ${waveIndex} task artifacts (.DONE, STATUS.md, REVIEW_VERDICT.json)`], { cwd: mergeWorkDir });
1978
+ spawnSync("git", ["commit", "-m", `checkpoint: wave ${waveIndex} task artifacts (.DONE, STATUS.md, REVIEW_VERDICT.json, .reviews/*)`], { cwd: mergeWorkDir });
1898
1979
  execLog("merge", `W${waveIndex}`, `committed ${staged} task artifact(s) to merge worktree`, {
1899
1980
  skipped,
1981
+ preserved,
1900
1982
  allowedCandidates: allowedRelPaths.size,
1901
1983
  });
1902
1984
  } else {
1903
- execLog("merge", `W${waveIndex}`, `no task artifacts to stage (0 of ${allowedRelPaths.size} candidates present/changed)`);
1985
+ execLog("merge", `W${waveIndex}`, `no task artifacts to stage (0 of ${allowedRelPaths.size} candidates present/changed, ${preserved} preserved from lane merge)`);
1904
1986
  }
1905
1987
 
1906
1988
  // Keep both .DONE and STATUS.md in develop's working tree:
@@ -770,6 +770,12 @@ You have these orchestrator tools available:
770
770
  **Note:** `orch_retry_task`, `orch_skip_task`, and `orch_force_merge` require the batch to be paused/stopped first.
771
771
  If the batch is actively running, call `orch_pause()` first.
772
772
 
773
+ **Diagnostic & Recovery Tools (TP-096):**
774
+ - `read_agent_status(lane?)` — Read STATUS.md + telemetry for a lane (step, progress, context %, cost, elapsed). Omit lane for all lanes.
775
+ - `trigger_wrap_up(lane)` — Write `.task-wrap-up` signal to gracefully stop a worker on a lane.
776
+ - `read_lane_logs(lane)` — Read stderr/crash logs and exit diagnostics for a lane.
777
+ - `list_active_agents()` — List all tmux sessions with role, lane, task, context %, elapsed, cost.
778
+
773
779
  Plus general tools: `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`
774
780
  for inspecting files, running git commands, and editing batch state.
775
781
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.22.12",
3
+ "version": "0.22.14",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -53,12 +53,11 @@ edit STATUS.md
53
53
 
54
54
  Then **check for wrap-up signal:**
55
55
  ```bash
56
- if test -f "<TASK_FOLDER>/.task-wrap-up" || test -f "<TASK_FOLDER>/.wiggum-wrap-up"; then
56
+ if test -f "<TASK_FOLDER>/.task-wrap-up"; then
57
57
  echo "WRAP_UP_SIGNAL"
58
58
  fi
59
59
  ```
60
- Primary signal file is `.task-wrap-up`; `.wiggum-wrap-up` is legacy and still supported.
61
- If either signal exists, STOP immediately after this checkpoint.
60
+ If the signal exists, STOP immediately after this checkpoint.
62
61
 
63
62
  If you do work but don't edit STATUS.md, that work is INVISIBLE to the
64
63
  orchestrator and you will be re-spawned to do it again.
@@ -262,6 +261,19 @@ Do NOT:
262
261
  - Modify docs listed in `task-runner.yaml → protected_docs` without explicit approval
263
262
  - Expand task scope — add tech debt instead
264
263
 
264
+ ## Steering Messages
265
+
266
+ During orchestrated runs, the supervisor may send steering messages to adjust
267
+ your approach. These messages appear in your conversation as user messages at
268
+ turn boundaries. They are also logged in the STATUS.md execution log as
269
+ `⚠️ Steering` entries for audit visibility.
270
+
271
+ When you receive a steering message:
272
+ 1. **Read it carefully** — it contains course corrections from the supervisor
273
+ 2. **Adjust your approach** as directed
274
+ 3. **Continue working** — do not stop or restart; incorporate the guidance naturally
275
+ 4. Steering messages are authoritative — treat them like direct instructions
276
+
265
277
  ## Error Handling
266
278
 
267
279
  - If stuck on the same issue after 3 attempts, document the blocker in STATUS.md