taskplane 0.28.7 → 0.28.8

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.
@@ -942,8 +942,11 @@ function renderLanesTasks(batch, sessions) {
942
942
  ? `<button class="viewer-eye-btn${isViewingStatus ? ' active' : ''}" onclick="viewStatusMd('${escapeHtml(task.taskId)}')" title="View STATUS.md">👁</button>`
943
943
  : '';
944
944
 
945
- // #485: Show task title (from PROMPT.md `# Task: <ID> - <title>`) under
946
- // the task-id when available. Falls back to just the ID when missing.
945
+ // #485 (revised): Show task title on a second grid row spanning task-id
946
+ // through progress (cols 3–6), giving ~5× more horizontal space than the
947
+ // original task-id-only width. Stops before col 7 (task-step + telemetry)
948
+ // so the step info and worker stats stay visible alongside the title.
949
+ // Falls back to no subtitle row when taskTitle is missing.
947
950
  const titleHtml = task.taskTitle
948
951
  ? `<div class="task-title-subtitle">${escapeHtml(task.taskTitle)}</div>`
949
952
  : "";
@@ -951,11 +954,12 @@ function renderLanesTasks(batch, sessions) {
951
954
  <div class="task-row">
952
955
  <span class="task-icon"><span class="status-dot ${task.status}"></span></span>
953
956
  <span class="task-actions">${eyeHtml}</span>
954
- <span class="task-id status-${task.status}"><div class="task-id-line">${escapeHtml(task.taskId)}${showRepos ? repoBadgeHtml(tRepo, "repo-badge-task") : ""}</div>${titleHtml}</span>
957
+ <span class="task-id status-${task.status}">${escapeHtml(task.taskId)}${showRepos ? repoBadgeHtml(tRepo, "repo-badge-task") : ""}</span>
955
958
  <span><span class="status-badge status-${task.status}"><span class="status-dot ${task.status}"></span> ${task.status}</span></span>
956
959
  <span class="task-duration">${dur}</span>
957
960
  <span>${progressHtml}</span>
958
961
  <span class="task-step">${stepHtml}${workerHtml}</span>
962
+ ${titleHtml}
959
963
  </div>`;
960
964
  html += reviewerRowHtml;
961
965
  }
@@ -607,8 +607,11 @@ body {
607
607
  .task-row {
608
608
  display: grid;
609
609
  grid-template-columns: 36px 24px 100px 90px 80px 200px 1fr;
610
+ /* #485 (revised): row 1 holds the primary cells; row 2 (auto, collapses to
611
+ * 0 when empty) holds the optional task-title-subtitle spanning cols 3–6. */
612
+ grid-template-rows: auto auto;
610
613
  align-items: center;
611
- gap: 8px;
614
+ gap: 8px 8px;
612
615
  padding: 8px 14px;
613
616
  border-bottom: 1px solid var(--border-subtle);
614
617
  transition: background 0.15s;
@@ -625,20 +628,19 @@ body {
625
628
  font-family: var(--font-mono);
626
629
  font-weight: 600;
627
630
  font-size: 0.85rem;
628
- /* #485: task-id may now contain a stacked task-id-line + task-title-subtitle */
629
- display: flex;
630
- flex-direction: column;
631
- gap: 2px;
632
- min-width: 0; /* allow inner ellipsis to work */
633
- }
634
-
635
- .task-id-line {
636
- /* The original TP-XXX line; preserves the previous single-line look */
637
- white-space: nowrap;
631
+ /* #485 (revised): task-id is back to a single inline cell. The optional
632
+ * title-subtitle is now a separate grid item placed in row 2 — it spans
633
+ * cols 3–6 (task-id through progress) for ~5× more horizontal space than
634
+ * the previous nested-subtitle approach. */
638
635
  }
639
636
 
640
637
  .task-title-subtitle {
641
- /* #485: human-readable task title under the ID */
638
+ /* #485 (revised): human-readable task title placed on row 2 of the task-row
639
+ * grid, spanning cols 3–6 (task-id, status, duration, progress) and
640
+ * stopping before col 7 (task-step + telemetry). Auto row 2 collapses to
641
+ * 0 height when this element is absent (no title for the task). */
642
+ grid-column: 3 / 7;
643
+ grid-row: 2;
642
644
  font-family: var(--font-sans, inherit);
643
645
  font-weight: 400;
644
646
  font-size: 0.72rem;
@@ -646,7 +648,8 @@ body {
646
648
  white-space: nowrap;
647
649
  overflow: hidden;
648
650
  text-overflow: ellipsis;
649
- max-width: 100%;
651
+ /* Pull subtitle visually closer to the task-id row above. */
652
+ margin-top: -2px;
650
653
  }
651
654
 
652
655
  .task-duration {
@@ -3,7 +3,7 @@
3
3
  * @module orch/worktree
4
4
  */
5
5
  import { existsSync, mkdirSync, readdirSync, realpathSync, rmdirSync, rmSync } from "fs";
6
- import { execSync } from "child_process";
6
+ import { execSync, execFileSync } from "child_process";
7
7
  import { join, basename, resolve } from "path";
8
8
 
9
9
  import { execLog } from "./execution.ts";
@@ -639,6 +639,57 @@ export function isRetriableRemoveError(stderr: string): boolean {
639
639
  return false;
640
640
  }
641
641
 
642
+ /**
643
+ * Detect Windows MAX_PATH ("Filename too long") errors from `git worktree remove`.
644
+ *
645
+ * On Windows with default `core.longpaths = false`, git refuses to delete
646
+ * paths that exceed MAX_PATH (260 characters). Deep `node_modules` trees
647
+ * commonly trip this. Native `cmd` `rd /s /q` uses a different deletion
648
+ * code path (NT object namespace, longer path tolerance) and usually
649
+ * succeeds where git fails.
650
+ *
651
+ * @param stderr - Error output from `git worktree remove`
652
+ * @returns true if the failure looks like the Windows MAX_PATH case
653
+ * @since TP-188 (#543)
654
+ */
655
+ export function isWindowsMaxPathError(stderr: string): boolean {
656
+ if (process.platform !== "win32") return false;
657
+ return /filename too long/i.test(stderr);
658
+ }
659
+
660
+ /**
661
+ * Run `cmd /c rd /s /q <path>` to recursively delete a directory on Windows.
662
+ *
663
+ * Used as a fallback after `git worktree remove` fails with the Windows
664
+ * MAX_PATH ("Filename too long") error. Caller must ensure platform is win32
665
+ * and the path is absolute. Path separators are normalized to backslashes
666
+ * because cmd's `rd` is more reliable with native Windows paths.
667
+ *
668
+ * @param absolutePath - Absolute path to remove (forward or back slashes accepted)
669
+ * @returns { ok, stdout, stderr }
670
+ * @since TP-188 (#543)
671
+ */
672
+ export function runWindowsCmdRd(
673
+ absolutePath: string,
674
+ ): { ok: boolean; stdout: string; stderr: string } {
675
+ const winPath = absolutePath.replace(/\//g, "\\");
676
+ try {
677
+ const stdout = execFileSync("cmd", ["/c", "rd", "/s", "/q", winPath], {
678
+ encoding: "utf-8",
679
+ timeout: 60_000,
680
+ stdio: ["pipe", "pipe", "pipe"],
681
+ }).toString().trim();
682
+ return { ok: true, stdout, stderr: "" };
683
+ } catch (err: unknown) {
684
+ const e = err as { stdout?: string; stderr?: string; message?: string };
685
+ return {
686
+ ok: false,
687
+ stdout: (e.stdout ?? "").toString().trim(),
688
+ stderr: (e.stderr ?? e.message ?? "unknown error").toString().trim(),
689
+ };
690
+ }
691
+ }
692
+
642
693
  /**
643
694
  * Remove a git worktree and clean up its associated branch.
644
695
  *
@@ -733,6 +784,50 @@ export function removeWorktree(
733
784
 
734
785
  lastError = removeResult.stderr;
735
786
 
787
+ // ── Windows MAX_PATH fallback (#543) ────────────────────────
788
+ // On Windows, `git worktree remove` fails with "Filename too long"
789
+ // when the worktree contains deep `node_modules` trees (most
790
+ // non-trivial Node projects) and `core.longpaths = false` (default).
791
+ // `cmd /c rd /s /q <path>` uses a different deletion code path
792
+ // that tolerates long paths better. Try it ONCE before classifying
793
+ // the error as terminal/retriable so other error classes still
794
+ // surface unchanged.
795
+ if (isWindowsMaxPathError(lastError)) {
796
+ execLog(
797
+ "cleanup",
798
+ "worktree",
799
+ `Windows MAX_PATH detected — falling back to cmd "rd /s /q"`,
800
+ { path: worktreePath, attempt },
801
+ );
802
+ const fallback = runWindowsCmdRd(worktreePath);
803
+ if (fallback.ok) {
804
+ execLog(
805
+ "cleanup",
806
+ "worktree",
807
+ `cmd "rd /s /q" fallback succeeded; pruning git worktree state`,
808
+ { path: worktreePath },
809
+ );
810
+ // The on-disk tree is gone; git's bookkeeping still has a
811
+ // stale entry. Prune so isRegisteredWorktree() returns false
812
+ // during post-removal verification below.
813
+ runGit(["worktree", "prune"], repoRoot);
814
+ break;
815
+ }
816
+ // Fallback also failed — enrich error so the operator sees both
817
+ // attempts, then fall through to the existing terminal/retry
818
+ // classification (which will throw because "Filename too long"
819
+ // is non-retriable per isRetriableRemoveError).
820
+ execLog(
821
+ "cleanup",
822
+ "worktree",
823
+ `cmd "rd /s /q" fallback failed`,
824
+ { path: worktreePath, error: fallback.stderr.slice(0, 200) },
825
+ );
826
+ lastError =
827
+ `git worktree remove failed: ${lastError}; ` +
828
+ `cmd rd /s /q fallback failed: ${fallback.stderr}`;
829
+ }
830
+
736
831
  // Check if error is terminal (non-retriable)
737
832
  if (!isRetriableRemoveError(lastError)) {
738
833
  throw new WorktreeError(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.28.7",
3
+ "version": "0.28.8",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -50,6 +50,74 @@ You handle a single review request and then exit.
50
50
  Do NOT just respond with text — the orchestrator reads the OUTPUT FILE to get
51
51
  your verdict. If you don't write the file, your review is lost.
52
52
 
53
+ ## Quality-check verification (code reviews only)
54
+
55
+ **This section applies to code reviews only.** For plan reviews, skip this
56
+ section entirely — there is no code to type-check or lint yet.
57
+
58
+ Before returning a code-review verdict, run the project's declared
59
+ typecheck / lint / format-check commands against the post-change tree. A
60
+ behavioural-correctness APPROVE is **invalidated** by failing quality checks.
61
+
62
+ The reviewer's tool allowlist already includes `bash`, so you can invoke these
63
+ commands directly — no special tooling is required.
64
+
65
+ ### How to discover the commands
66
+
67
+ 1. **Project config first.** Read `.pi/taskplane-config.json` (or the legacy
68
+ `.pi/task-runner.yaml` / `.pi/task-runner.json` fallbacks) and look at
69
+ `taskRunner.testing.commands` — a `Record<string, string>` mapping a
70
+ command name (e.g. `typecheck`, `lint`, `format:check`) to a
71
+ shell command. Run any command whose key matches one of
72
+ `typecheck` / `tsc` / `types` / `lint` / `format:check`.
73
+ **Prefer `format:check` over `format`** — the latter typically rewrites
74
+ files in place, which would mutate the working tree the reviewer is
75
+ evaluating. If only a mutating `format` script is available in either
76
+ source, skip it and note this in the Summary; do not run mutating
77
+ commands from the reviewer.
78
+ 2. **Fallback to `package.json` scripts.** If step 1 did not yield any
79
+ relevant commands — either because `taskRunner.testing.commands` is
80
+ absent OR because it exists but contains no keys matching the
81
+ typecheck/lint/format-check set — read `package.json` and run any of
82
+ these scripts that exist, in this order:
83
+ `npm run typecheck`, `npm run lint`, `npm run format:check`.
84
+ Skip a script if `package.json#scripts` does not declare it — do not
85
+ invent commands.
86
+ 3. **Skip silently** if neither source yields a relevant command. Do not fail
87
+ the review just because the project has no quality-check pipeline
88
+ configured. Note this in the Summary so the operator knows quality checks
89
+ were not exercised.
90
+
91
+ Do NOT run the project's full test suite from this section — that is the
92
+ worker's Testing & Verification step. The quality checks here are
93
+ **fast static checks** (typecheck, lint, format) that are cheap to run and
94
+ high-signal for catching regressions the behavioural diff review would miss.
95
+
96
+ ### What to do with the results
97
+
98
+ - **All quality checks pass** → proceed to behavioural code review as normal.
99
+ - **A quality check fails** → surface each failing command as an entry in
100
+ **Issues Found** with severity `important`. Include:
101
+ - The command that failed (e.g. `npm run typecheck`)
102
+ - The first few lines of the failing output (file/line locations are
103
+ most useful)
104
+ - A concrete suggested fix where the failure makes one obvious
105
+ - **Verdict downgrade rule:** If quality checks fail, the verdict is
106
+ **REVISE** — even if the behavioural code review would otherwise have
107
+ been APPROVE. Quality-check failures are blocking by definition: they
108
+ would surface at the worker's Testing & Verification step and force a
109
+ redo of the entire review cycle, so it is strictly cheaper to surface
110
+ them here.
111
+
112
+ ### Worked example (Issues Found entry)
113
+
114
+ ```
115
+ 1. **[npm run typecheck:1] [important]** — 5 strict-mode errors in
116
+ tests/foo.test.ts. Sample: "Argument of type 'undefined' is not
117
+ assignable to parameter of type 'string'" at line 42. Fix: narrow
118
+ `getThing()` return type or assert non-null at call site.
119
+ ```
120
+
53
121
  ## Verdict Criteria
54
122
 
55
123
  - **APPROVE** — Step will achieve its stated outcomes. Minor suggestions belong