pi-crew 0.11.0 → 0.11.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.
Files changed (174) hide show
  1. package/CHANGELOG.md +155 -9
  2. package/README.md +161 -1037
  3. package/agents/verifier.md +18 -7
  4. package/dist/index.mjs +744 -90644
  5. package/docs/README.md +57 -46
  6. package/docs/architecture.md +87 -33
  7. package/docs/commands-reference.md +9 -5
  8. package/docs/troubleshooting.md +3 -2
  9. package/package.json +1 -3
  10. package/schema.json +39 -0
  11. package/skills/real-test-pi-crew/REPORT-TEMPLATE.md +2 -0
  12. package/skills/real-test-pi-crew/SKILL.md +371 -34
  13. package/src/agents/agent-config.ts +1 -1
  14. package/src/agents/discover-agents.ts +1 -1
  15. package/src/config/config-validation.ts +15 -2
  16. package/src/config/config.ts +47 -13
  17. package/src/config/defaults.ts +0 -1
  18. package/src/config/env-vars.ts +35 -0
  19. package/src/config/types.ts +19 -5
  20. package/src/errors.ts +2 -2
  21. package/src/extension/async-notifier.ts +23 -0
  22. package/src/extension/crew-vibes/config.ts +0 -21
  23. package/src/extension/crew-vibes/index.ts +0 -2
  24. package/src/extension/crew-vibes/render.ts +1 -50
  25. package/src/extension/help.ts +21 -12
  26. package/src/extension/knowledge-injection.ts +2 -1
  27. package/src/extension/management.ts +8 -3
  28. package/src/extension/notification-sink.ts +17 -0
  29. package/src/extension/register.ts +7 -2
  30. package/src/extension/registration/command-utils.ts +28 -2
  31. package/src/extension/registration/commands/dashboard.ts +11 -1
  32. package/src/extension/registration/commands/manage.ts +36 -19
  33. package/src/extension/registration/commands/run.ts +24 -2
  34. package/src/extension/registration/commands/shared.ts +23 -1
  35. package/src/extension/registration/commands/status.ts +25 -2
  36. package/src/extension/registration/context-builder.ts +8 -2
  37. package/src/extension/registration/health-notify-policy.ts +100 -0
  38. package/src/extension/registration/lazy-configurers.ts +35 -0
  39. package/src/extension/registration/lifecycle-handlers.ts +91 -30
  40. package/src/extension/registration/lifecycle.ts +75 -10
  41. package/src/extension/registration/observability.ts +98 -35
  42. package/src/extension/registration/registration-types.ts +7 -5
  43. package/src/extension/registration/runtime-cleanup.ts +9 -3
  44. package/src/extension/registration/subagent-helpers.ts +38 -0
  45. package/src/extension/registration/subagent-tools.ts +16 -6
  46. package/src/extension/registration/team-tool.ts +10 -3
  47. package/src/extension/registration/terminal-status-wiring.ts +172 -0
  48. package/src/extension/registration/viewers.ts +6 -0
  49. package/src/extension/registration/wire-cross-extension.ts +28 -0
  50. package/src/extension/run-compare.ts +220 -0
  51. package/src/extension/run-export.ts +37 -5
  52. package/src/extension/run-maintenance.ts +155 -5
  53. package/src/extension/team-tool/dispatch/index.ts +3 -2
  54. package/src/extension/team-tool/dispatch/manage.ts +5 -2
  55. package/src/extension/team-tool/goal.ts +4 -1
  56. package/src/extension/team-tool/handle-settings.ts +33 -4
  57. package/src/extension/team-tool/health-monitor.ts +21 -7
  58. package/src/extension/team-tool/lifecycle-actions.ts +49 -1
  59. package/src/extension/team-tool/plan.ts +10 -0
  60. package/src/extension/team-tool/routing-hint.ts +63 -0
  61. package/src/extension/team-tool/status.ts +4 -0
  62. package/src/extension/team-tool.ts +52 -6
  63. package/src/extension/webhook-notify.ts +382 -0
  64. package/src/observability/metric-sink.ts +12 -2
  65. package/src/prompt/prompt-runtime.ts +82 -31
  66. package/src/prompt/worker-events-channel.ts +12 -0
  67. package/src/runtime/README.md +1 -1
  68. package/src/runtime/async-runner.ts +87 -1
  69. package/src/runtime/background-runner.ts +313 -234
  70. package/src/runtime/broker/crew-broker.ts +17 -11
  71. package/src/runtime/broker/delegate/shadow-lifecycle.ts +92 -0
  72. package/src/runtime/broker/wait-status-cache.ts +1 -1
  73. package/src/runtime/child-pi/child-pi-timers.ts +1 -1
  74. package/src/runtime/child-pi/mock-fixtures.ts +48 -0
  75. package/src/runtime/crew-agent-records.ts +337 -45
  76. package/src/runtime/deadletter.ts +43 -1
  77. package/src/runtime/delegate-spawn.ts +5 -1
  78. package/src/runtime/dispatch-batch.ts +72 -5
  79. package/src/runtime/goal-workflow/goal-loop-runner.ts +73 -4
  80. package/src/runtime/heartbeat/heartbeat-watcher.ts +7 -0
  81. package/src/runtime/model/model-fallback.ts +21 -1
  82. package/src/runtime/model/pi-args.ts +8 -10
  83. package/src/runtime/recovery/crash-recovery.ts +25 -1
  84. package/src/runtime/run-worker.ts +12 -1
  85. package/src/runtime/scheduling/global-worker-cap.ts +13 -6
  86. package/src/runtime/scheduling/run-coalesced-task-group.ts +27 -1
  87. package/src/runtime/scheduling/scheduler.ts +49 -13
  88. package/src/runtime/scheduling/semaphore.ts +148 -20
  89. package/src/runtime/scratchpad/README.md +1 -1
  90. package/src/runtime/scratchpad/protocol.ts +1 -1
  91. package/src/runtime/settings-store.ts +1 -1
  92. package/src/runtime/skill-instructions.ts +22 -0
  93. package/src/runtime/stale-reconciler.ts +85 -13
  94. package/src/runtime/task-display.ts +1 -1
  95. package/src/runtime/task-runner/pre-execution.ts +26 -2
  96. package/src/runtime/task-runner/prompt-builder.ts +142 -45
  97. package/src/runtime/task-runner.ts +21 -1
  98. package/src/runtime/team-runner.ts +38 -1
  99. package/src/runtime/workspace-lock.ts +4 -1
  100. package/src/schema/config-schema.ts +18 -0
  101. package/src/schema/team-tool-schema.ts +17 -0
  102. package/src/state/atomic-write.ts +53 -0
  103. package/src/state/contracts.ts +109 -0
  104. package/src/state/coordination/locks.ts +191 -33
  105. package/src/state/coordination/mailbox.ts +140 -15
  106. package/src/state/crew-init.ts +87 -12
  107. package/src/state/event-log/cursor.ts +37 -1
  108. package/src/state/event-log/event-log-rotation.ts +72 -7
  109. package/src/state/stores/active-run-registry.ts +13 -1
  110. package/src/state/stores/state-store.ts +112 -22
  111. package/src/state/types.ts +4 -0
  112. package/src/ui/adaptive-card.ts +65 -0
  113. package/src/ui/agents-jobs-browser.ts +70 -64
  114. package/src/ui/card-colors.ts +36 -7
  115. package/src/ui/dashboard-panes/agents-pane.ts +55 -14
  116. package/src/ui/dashboard-panes/cancellation-pane.ts +0 -42
  117. package/src/ui/dashboard-panes/health-pane.ts +7 -5
  118. package/src/ui/dashboard-panes/mailbox-pane.ts +22 -6
  119. package/src/ui/dashboard-panes/metrics-pane.ts +15 -7
  120. package/src/ui/dashboard-panes/pane-theme.ts +21 -0
  121. package/src/ui/dashboard-panes/plan-pane.ts +63 -30
  122. package/src/ui/dashboard-panes/progress-pane.ts +3 -2
  123. package/src/ui/dashboard-panes/schedules-pane.ts +44 -21
  124. package/src/ui/dashboard-panes/transcript-pane.ts +11 -5
  125. package/src/ui/dwf-phase-display.ts +3 -20
  126. package/src/ui/format-helpers.ts +22 -0
  127. package/src/ui/heartbeat-aggregator.ts +34 -0
  128. package/src/ui/inline-panel/crew-editor.ts +13 -3
  129. package/src/ui/inline-panel/index.ts +60 -4
  130. package/src/ui/keybinding-map.ts +251 -35
  131. package/src/ui/live-conversation-overlay.ts +180 -47
  132. package/src/ui/live-run-sidebar.ts +134 -55
  133. package/src/ui/mascot.ts +32 -16
  134. package/src/ui/overlays/agent-picker-overlay.ts +81 -26
  135. package/src/ui/overlays/confirm-overlay.ts +55 -29
  136. package/src/ui/overlays/help-overlay.ts +108 -53
  137. package/src/ui/overlays/mailbox-compose-overlay.ts +89 -50
  138. package/src/ui/overlays/mailbox-detail-overlay.ts +137 -57
  139. package/src/ui/powerbar-publisher.ts +0 -1
  140. package/src/ui/rail.ts +333 -0
  141. package/src/ui/run-dashboard.ts +193 -79
  142. package/src/ui/run-snapshot-cache.ts +18 -1
  143. package/src/ui/settings-overlay.ts +81 -39
  144. package/src/ui/spinner.ts +26 -2
  145. package/src/ui/terminal-status.ts +7 -1
  146. package/src/ui/theme-adapter.ts +0 -45
  147. package/src/ui/theme-discovery.ts +12 -6
  148. package/src/ui/tool-progress-formatter.ts +128 -9
  149. package/src/ui/tool-renderers/brief-mode.ts +10 -67
  150. package/src/ui/tool-renderers/index.ts +374 -523
  151. package/src/ui/transcript-viewer.ts +30 -12
  152. package/src/ui/widget/index.ts +32 -52
  153. package/src/ui/widget/task-list.ts +64 -32
  154. package/src/ui/widget/widget-formatters.ts +3 -402
  155. package/src/ui/widget/widget-model.ts +28 -7
  156. package/src/ui/widget/widget-renderer.ts +201 -128
  157. package/src/ui/widget/widget-types.ts +0 -2
  158. package/src/utils/incremental-reader.ts +11 -3
  159. package/src/utils/paths.ts +94 -12
  160. package/src/utils/project-markers.ts +40 -0
  161. package/src/utils/visual.ts +0 -4
  162. package/src/worktree/worktree-manager.ts +206 -26
  163. package/workflows/distill.workflow.md +3 -3
  164. package/workflows/fast-fix.workflow.md +1 -1
  165. package/workflows/plan-execute.workflow.md +1 -1
  166. package/workflows/review.workflow.md +1 -1
  167. package/workflows/strict-fast-fix.workflow.md +1 -1
  168. package/docs/migration-v0.4-v0.5.md +0 -208
  169. package/docs/runtime-flow.md +0 -148
  170. package/src/extension/crew-vibes/figures.ts +0 -22
  171. package/src/extension/crew-vibes/font-detect.ts +0 -71
  172. package/src/ui/dynamic-border.ts +0 -35
  173. package/src/ui/loaders.ts +0 -6
  174. package/src/ui/overlay-stack.ts +0 -148
@@ -21,6 +21,19 @@ export interface PreparedTaskWorkspace {
21
21
  syntheticPaths?: string[];
22
22
  }
23
23
 
24
+ /**
25
+ * Options for prepareTaskWorkspace / prepareTaskWorkspaceAsync (RR-010).
26
+ */
27
+ export interface PrepareTaskWorkspaceOptions {
28
+ /**
29
+ * RR-010 / AGENTS.md rule 40: allow discarding a dirty reused worktree EVEN
30
+ * WHEN the recovery snapshot is incomplete (truncated/skipped entries, failed
31
+ * tracked diff). Explicit per-call escape hatch — deliberately NOT a global
32
+ * config flag, so approval cannot silently become permanent.
33
+ */
34
+ force?: boolean;
35
+ }
36
+
24
37
  const execFileAsync = promisify(execFile);
25
38
 
26
39
  export interface WorktreeDiffStat {
@@ -657,14 +670,140 @@ export function overlaySeedPaths(repoRoot: string, worktreePath: string, seedPat
657
670
  }
658
671
  }
659
672
 
673
+ /**
674
+ * Per-file byte cap for the recovery-snapshot PREVIEW (ST-1, kept at 256 KiB so
675
+ * the artifact stays readable). RR-010 (F01): the cap now ALSO bounds how many
676
+ * bytes are read (allocation bound) and marks over-cap entries as an
677
+ * INCOMPLETE backup — completeness, not preview size, gates the destructive
678
+ * reuse cleanup (AGENTS.md rule 40).
679
+ */
680
+ export const SNAPSHOT_MAX_FILE_BYTES = 256 * 1024;
681
+
682
+ /** Entry whose backup was cut at SNAPSHOT_MAX_FILE_BYTES (partial capture). */
683
+ export interface WorktreeSnapshotTruncatedEntry {
684
+ /** Repo-relative path of the entry. */
685
+ path: string;
686
+ /** On-disk size in bytes BEFORE truncation (not the truncated size). */
687
+ originalSize: number;
688
+ }
689
+
690
+ /** Entry that could not be backed up at all — absent from the artifact content. */
691
+ export interface WorktreeSnapshotSkippedEntry {
692
+ /** Repo-relative path of the entry. */
693
+ path: string;
694
+ /** Why the entry was skipped (unreadable, unstat-able, missing, directory...). */
695
+ reason: string;
696
+ }
697
+
698
+ /**
699
+ * Structured result of a dirty-worktree recovery snapshot (RR-010 / F01).
700
+ *
701
+ * A bare boolean could not distinguish "artifact written" from "backup
702
+ * complete": truncation at the cap, unreadable entries and a failed tracked
703
+ * diff all used to return `true`, so the reuse path ran `git checkout -- .` +
704
+ * `git clean -fd` and destroyed data the artifact never contained. `complete`
705
+ * is the invariant `truncated/skipped empty && no diff/write error` — callers
706
+ * must gate the destructive cleanup on it via shouldDiscardDirtyWorktree.
707
+ */
708
+ export interface WorktreeSnapshotResult {
709
+ /** True ONLY when every dirty entry was fully backed up and the tracked diff (if any) was captured. */
710
+ complete: boolean;
711
+ /** Entries backed up only partially (cut at the preview cap). */
712
+ truncated: WorktreeSnapshotTruncatedEntry[];
713
+ /** Entries that could not be backed up at all. */
714
+ skipped: WorktreeSnapshotSkippedEntry[];
715
+ /** Set when `git diff HEAD --binary` failed — tracked changes may be lost. */
716
+ trackedDiffError?: string;
717
+ /** Set when writing the recovery artifact itself failed (the old `false`). */
718
+ writeError?: string;
719
+ }
720
+
721
+ /**
722
+ * Pure, git-free gate for the destructive reuse cleanup (RR-010 AC4): discard
723
+ * dirty worktree content ONLY when the snapshot is complete, or when the caller
724
+ * explicitly set `force` (AGENTS.md rule 40). Fail CLOSED — when in doubt,
725
+ * preserve the worktree.
726
+ */
727
+ export function shouldDiscardDirtyWorktree(result: WorktreeSnapshotResult, force: boolean): boolean {
728
+ return force || result.complete;
729
+ }
730
+
731
+ /**
732
+ * Allocation-bounded file read for the recovery snapshot (RR-010 AC5).
733
+ * Opens the file and reads at most `cap` bytes — `readFileSync` would allocate
734
+ * the WHOLE file in memory just to truncate it afterwards, so a multi-GiB
735
+ * untracked file could OOM the snapshot path. A short read (file shrank
736
+ * mid-read) is reported as truncated so it can never count as a complete
737
+ * backup (fail closed).
738
+ */
739
+ export function readFileCappedForSnapshot(abs: string, cap: number): { data: Buffer; originalSize: number; truncated: boolean } {
740
+ const fd = fs.openSync(abs, "r");
741
+ try {
742
+ const originalSize = fs.fstatSync(fd).size;
743
+ const want = Math.min(originalSize, cap);
744
+ const data = Buffer.alloc(want);
745
+ let read = 0;
746
+ while (read < want) {
747
+ const n = fs.readSync(fd, data, read, want - read, read);
748
+ if (n <= 0) break;
749
+ read += n;
750
+ }
751
+ return {
752
+ data: read === want ? data : data.subarray(0, read),
753
+ originalSize,
754
+ truncated: read < want || originalSize > cap,
755
+ };
756
+ } finally {
757
+ fs.closeSync(fd);
758
+ }
759
+ }
760
+
761
+ /** Human-readable incompleteness summary for the dirtyPreserved log (RR-010). */
762
+ function describeIncompleteSnapshot(result: WorktreeSnapshotResult): string {
763
+ const reasons: string[] = [];
764
+ if (result.writeError !== undefined) reasons.push(`snapshot write failed: ${result.writeError}`);
765
+ if (result.trackedDiffError !== undefined) reasons.push(`tracked diff capture failed: ${result.trackedDiffError}`);
766
+ if (result.truncated.length > 0)
767
+ reasons.push(
768
+ `${result.truncated.length} file(s) exceeded the ${SNAPSHOT_MAX_FILE_BYTES}-byte cap: ${result.truncated
769
+ .map((e) => `${e.path} (${e.originalSize} bytes)`)
770
+ .join(", ")}`,
771
+ );
772
+ if (result.skipped.length > 0)
773
+ reasons.push(
774
+ `${result.skipped.length} unreadable/unstat-able entries: ${result.skipped.map((e) => `${e.path} (${e.reason})`).join(", ")}`,
775
+ );
776
+ return reasons.length > 0 ? ` — ${reasons.join("; ")}` : "";
777
+ }
778
+
660
779
  /**
661
780
  * Snapshot uncommitted work in a reused worktree to a recovery artifact BEFORE
662
781
  * discarding it, so the data is never silently lost. Captures tracked changes
663
782
  * (git diff HEAD) and inlines untracked file contents so the work can be
664
- * recovered via `git apply` / manual restore. Best-effort: a snapshot failure
665
- * only logs (it must not block the clean-slate reuse flow).
783
+ * recovered via `git apply` / manual restore.
784
+ *
785
+ * RR-010 (F01) — returns a structured WorktreeSnapshotResult. An artifact that
786
+ * was written is NOT proof the backup is complete: truncated (over-cap /
787
+ * short-read) entries, unreadable entries and a failed tracked diff all surface
788
+ * in the result so callers can refuse the destructive cleanup
789
+ * (shouldDiscardDirtyWorktree). Design choice (RR-010 design.md §3–§4):
790
+ * structured result + pure predicate over raising the cap (moves the boundary,
791
+ * does not remove it), louder warnings (violates AGENTS.md rule 40), full
792
+ * base64 (artifact size explodes), dropping cleanup entirely (breaks
793
+ * clean-slate reuse; `force` stays as the escape hatch) and `git stash` (also a
794
+ * destructive git op with no artifact trail). The 256 KiB PREVIEW cap is kept
795
+ * on purpose — backup completeness, not preview size, gates cleanup.
666
796
  */
667
- export function snapshotDirtyWorktree(manifest: TeamRunManifest, task: TeamTaskState, worktreePath: string, dirtyStatus: string): boolean {
797
+ export function snapshotDirtyWorktree(
798
+ manifest: TeamRunManifest,
799
+ task: TeamTaskState,
800
+ worktreePath: string,
801
+ dirtyStatus: string,
802
+ ): WorktreeSnapshotResult {
803
+ const truncated: WorktreeSnapshotTruncatedEntry[] = [];
804
+ const skipped: WorktreeSnapshotSkippedEntry[] = [];
805
+ let trackedDiffError: string | undefined;
806
+ let writeError: string | undefined;
668
807
  try {
669
808
  const parts: string[] = [
670
809
  `# Worktree recovery snapshot`,
@@ -676,14 +815,16 @@ export function snapshotDirtyWorktree(manifest: TeamRunManifest, task: TeamTaskS
676
815
  let trackedDiff = "";
677
816
  try {
678
817
  trackedDiff = git(worktreePath, ["diff", "HEAD", "--binary"]);
679
- } catch {
818
+ } catch (err) {
819
+ // RR-010: "" stays reserved for the legitimate "no tracked changes" outcome —
820
+ // a FAILED diff is an incomplete backup (tracked edits would be destroyed by
821
+ // `git checkout -- .` with no copy anywhere).
680
822
  trackedDiff = "";
823
+ trackedDiffError = err instanceof Error ? err.message : String(err);
681
824
  }
682
825
  if (trackedDiff.trim()) {
683
826
  parts.push("## Tracked changes (`git diff HEAD --binary`)", "```diff", trackedDiff, "```", "");
684
827
  }
685
- // ST-1: per-file byte cap — skip/truncate larger files with a note to avoid OOM.
686
- const MAX_FILE_BYTES = 256 * 1024;
687
828
  for (const line of dirtyStatus.split("\n")) {
688
829
  if (!line.startsWith("?? ")) continue;
689
830
  // Strip the "?? " prefix and surrounding git quotes.
@@ -691,15 +832,24 @@ export function snapshotDirtyWorktree(manifest: TeamRunManifest, task: TeamTaskS
691
832
  if (!rel) continue;
692
833
  try {
693
834
  const abs = path.join(worktreePath, rel);
694
- if (!fs.existsSync(abs) || fs.statSync(abs).isDirectory()) continue;
835
+ if (!fs.existsSync(abs)) {
836
+ // RR-010: record instead of silently `continue` — a vanished entry is
837
+ // unexplained and must fail CLOSED (preserve, do not clean).
838
+ skipped.push({ path: rel, reason: "not found (deleted between status and snapshot?)" });
839
+ continue;
840
+ }
841
+ if (fs.statSync(abs).isDirectory()) {
842
+ skipped.push({ path: rel, reason: "directory entry — individual files must be captured instead" });
843
+ continue;
844
+ }
695
845
  // ST-1: read raw bytes (not utf-8) so binary files are not corrupted.
696
- const buf = fs.readFileSync(abs);
697
- const originalSize = buf.byteLength;
698
- let data: Buffer = buf;
846
+ // RR-010: bounded read — never allocate more than the cap for a file that
847
+ // is only going to be truncated anyway (AC5).
848
+ const { data, originalSize, truncated: overCap } = readFileCappedForSnapshot(abs, SNAPSHOT_MAX_FILE_BYTES);
699
849
  let note = "";
700
- if (originalSize > MAX_FILE_BYTES) {
701
- data = buf.subarray(0, MAX_FILE_BYTES);
702
- note = ` (truncated: ${originalSize} → ${MAX_FILE_BYTES} bytes)`;
850
+ if (overCap) {
851
+ truncated.push({ path: rel, originalSize });
852
+ note = ` (truncated: ${originalSize} → ${data.byteLength} bytes)`;
703
853
  }
704
854
  // ST-1: detect non-UTF-8 via fatal TextDecoder; base64-encode binary files.
705
855
  let isUtf8 = true;
@@ -713,10 +863,22 @@ export function snapshotDirtyWorktree(manifest: TeamRunManifest, task: TeamTaskS
713
863
  } else {
714
864
  parts.push(`## Untracked file: ${rel}${note} (base64-encoded binary)`, "```base64", data.toString("base64"), "```", "");
715
865
  }
716
- } catch {
717
- /* skip unreadable/unstat-able entry */
866
+ } catch (err) {
867
+ // RR-010: unreadable/unstat-able entries are recorded (and named in the
868
+ // artifact below) instead of being silently dropped — they used to be
869
+ // destroyed by `git clean -fd` with ZERO recovery.
870
+ skipped.push({ path: rel, reason: err instanceof Error ? err.message : String(err) });
718
871
  }
719
872
  }
873
+ // RR-010 (AC3): skipped entries MUST be named in the artifact so operators
874
+ // can see exactly which files were NOT backed up before deciding to force a
875
+ // cleanup — an artifact silent about them is false evidence.
876
+ if (skipped.length > 0) {
877
+ parts.push("## Skipped entries (NOT backed up)", ...skipped.map((s) => `- ${s.path} — ${s.reason}`), "");
878
+ }
879
+ if (trackedDiffError !== undefined) {
880
+ parts.push("## Tracked diff capture FAILED — tracked modifications are NOT backed up", "```", trackedDiffError, "```", "");
881
+ }
720
882
  writeArtifact(manifest.artifactsRoot, {
721
883
  kind: "diff",
722
884
  relativePath: `worktree-recovery/${task.id}-${Date.now()}.md`,
@@ -724,15 +886,16 @@ export function snapshotDirtyWorktree(manifest: TeamRunManifest, task: TeamTaskS
724
886
  producer: "worktree-manager.snapshotDirtyWorktree",
725
887
  retention: "run",
726
888
  });
727
- return true;
728
889
  } catch (err) {
729
890
  logInternalError(
730
891
  "worktree.recovery.snapshotFailed",
731
892
  err instanceof Error ? err : new Error(String(err)),
732
893
  `runId=${manifest.runId}, taskId=${task.id}`,
733
894
  );
734
- return false;
895
+ writeError = err instanceof Error ? err.message : String(err);
735
896
  }
897
+ const complete = truncated.length === 0 && skipped.length === 0 && trackedDiffError === undefined && writeError === undefined;
898
+ return { complete, truncated, skipped, trackedDiffError, writeError };
736
899
  }
737
900
 
738
901
  /**
@@ -775,7 +938,12 @@ async function cleanupCreatedWorktreeAsync(repoRoot: string, worktreePath: strin
775
938
  }
776
939
  }
777
940
 
778
- export function prepareTaskWorkspace(manifest: TeamRunManifest, task: TeamTaskState, stepSeedPaths?: string[]): PreparedTaskWorkspace {
941
+ export function prepareTaskWorkspace(
942
+ manifest: TeamRunManifest,
943
+ task: TeamTaskState,
944
+ stepSeedPaths?: string[],
945
+ options?: PrepareTaskWorkspaceOptions,
946
+ ): PreparedTaskWorkspace {
779
947
  if (manifest.workspaceMode !== "worktree") return { cwd: task.cwd };
780
948
  const repoRoot = findGitRoot(manifest.cwd);
781
949
  const loadedConfig = loadConfig(manifest.cwd);
@@ -852,11 +1020,17 @@ export function prepareTaskWorkspace(manifest: TeamRunManifest, task: TeamTaskSt
852
1020
  if (dirtyStatus.trim()) {
853
1021
  // Snapshot uncommitted work to a recovery artifact BEFORE discarding, so the
854
1022
  // previous run's changes are never silently destroyed on reuse.
855
- const snapshotOk = snapshotDirtyWorktree(manifest, task, worktreePath, dirtyStatus);
856
- if (snapshotOk) {
1023
+ // RR-010 (F01): discard only when the snapshot is COMPLETE (or the caller
1024
+ // explicitly forced it) — AGENTS.md rule 40. An artifact that was written
1025
+ // is not proof the backup is complete; fail CLOSED and preserve.
1026
+ const snapshotResult = snapshotDirtyWorktree(manifest, task, worktreePath, dirtyStatus);
1027
+ if (shouldDiscardDirtyWorktree(snapshotResult, options?.force === true)) {
857
1028
  logInternalError(
858
1029
  "worktree.reused.dirty",
859
- new Error(`Discarding uncommitted changes in reused worktree at ${worktreePath} (snapshot saved to artifacts)`),
1030
+ new Error(
1031
+ `Discarding uncommitted changes in reused worktree at ${worktreePath} (snapshot saved to artifacts)` +
1032
+ (snapshotResult.complete ? "" : " — SNAPSHOT INCOMPLETE, discarded because caller set force=true"),
1033
+ ),
860
1034
  `runId=${manifest.runId}, taskId=${task.id}, dirtyStatus=${dirtyStatus.trim()}`,
861
1035
  );
862
1036
  git(worktreePath, ["checkout", "--", "."]);
@@ -865,7 +1039,7 @@ export function prepareTaskWorkspace(manifest: TeamRunManifest, task: TeamTaskSt
865
1039
  logInternalError(
866
1040
  "worktree.reused.dirtyPreserved",
867
1041
  new Error(
868
- `Snapshot failed — preserving dirty worktree at ${worktreePath} (skipping git checkout/clean to prevent data loss)`,
1042
+ `Snapshot incomplete — preserving dirty worktree at ${worktreePath} (skipping git checkout/clean to prevent data loss)${describeIncompleteSnapshot(snapshotResult)}`,
869
1043
  ),
870
1044
  `runId=${manifest.runId}, taskId=${task.id}, dirtyStatus=${dirtyStatus.trim()}`,
871
1045
  );
@@ -948,6 +1122,7 @@ export async function prepareTaskWorkspaceAsync(
948
1122
  manifest: TeamRunManifest,
949
1123
  task: TeamTaskState,
950
1124
  stepSeedPaths?: string[],
1125
+ options?: PrepareTaskWorkspaceOptions,
951
1126
  ): Promise<PreparedTaskWorkspace> {
952
1127
  if (manifest.workspaceMode !== "worktree") return { cwd: task.cwd };
953
1128
  const repoRoot = await findGitRootAsync(manifest.cwd);
@@ -1012,11 +1187,16 @@ export async function prepareTaskWorkspaceAsync(
1012
1187
  // ST-1: use -uall so untracked directories are expanded to individual files.
1013
1188
  const dirtyStatus = await gitAsync(worktreePath, ["-c", "core.quotePath=false", "status", "--porcelain", "-uall"]);
1014
1189
  if (dirtyStatus.trim()) {
1015
- const snapshotOk = snapshotDirtyWorktree(manifest, task, worktreePath, dirtyStatus);
1016
- if (snapshotOk) {
1190
+ // RR-010 (F01): mirror of the sync gate — discard only when the snapshot is
1191
+ // COMPLETE (or explicitly forced). AGENTS.md rule 40. Fail CLOSED.
1192
+ const snapshotResult = snapshotDirtyWorktree(manifest, task, worktreePath, dirtyStatus);
1193
+ if (shouldDiscardDirtyWorktree(snapshotResult, options?.force === true)) {
1017
1194
  logInternalError(
1018
1195
  "worktree.reused.dirty",
1019
- new Error(`Discarding uncommitted changes in reused worktree at ${worktreePath} (snapshot saved to artifacts)`),
1196
+ new Error(
1197
+ `Discarding uncommitted changes in reused worktree at ${worktreePath} (snapshot saved to artifacts)` +
1198
+ (snapshotResult.complete ? "" : " — SNAPSHOT INCOMPLETE, discarded because caller set force=true"),
1199
+ ),
1020
1200
  `runId=${manifest.runId}, taskId=${task.id}, dirtyStatus=${dirtyStatus.trim()}`,
1021
1201
  );
1022
1202
  await gitAsync(worktreePath, ["checkout", "--", "."]);
@@ -1025,7 +1205,7 @@ export async function prepareTaskWorkspaceAsync(
1025
1205
  logInternalError(
1026
1206
  "worktree.reused.dirtyPreserved",
1027
1207
  new Error(
1028
- `Snapshot failed — preserving dirty worktree at ${worktreePath} (skipping git checkout/clean to prevent data loss)`,
1208
+ `Snapshot incomplete — preserving dirty worktree at ${worktreePath} (skipping git checkout/clean to prevent data loss)${describeIncompleteSnapshot(snapshotResult)}`,
1029
1209
  ),
1030
1210
  `runId=${manifest.runId}, taskId=${task.id}, dirtyStatus=${dirtyStatus.trim()}`,
1031
1211
  );
@@ -144,7 +144,7 @@ output: apply-plan.md
144
144
  - **HOW to apply**: AGENTS.md edit, lint rule add, src/ pattern adopt, CONTRIBUTING.md update, scripts/ operational script, or skill in target's skills/ dir
145
145
  - **Exact file:line target** in the target project
146
146
  - **Tier priority**: Tier 1 (high V3, low V4 cost) → Tier 2 (high V3, medium V4 cost) → Tier 3 (high V3, high V4 cost — pilot first)
147
- - **Verification gate per item**: `npm run test:critical` + `npm run typecheck` + `npm run build:bundle` must pass after each apply; bundle MD5 must match or be updated; if tests fail → rollback
147
+ - **Verification gate per item**: `npm run test:critical` + `npm run typecheck` + `npm run build:bundle` must pass after each apply; bundle MD5 must match or be updated; if tests fail → rollback. (If the TARGET project is not this repo or lacks these scripts, use its fastest documented check gate instead — never run a full suite per item.)
148
148
 
149
149
  Write to `references/apply-plan.md`. This is the input for the executor in Phase 4.
150
150
 
@@ -157,7 +157,7 @@ output: applied-changes.md
157
157
 
158
158
  Per-item protocol:
159
159
  1. Edit the target file(s) as specified in apply-plan.md
160
- 2. Run verification gate: `npm run test:critical` (or equivalent) + `npm run typecheck` + `npm run build:bundle`
160
+ 2. Run verification gate: `npm run test:critical` (or the target's fastest documented check) + `npm run typecheck` + `npm run build:bundle`
161
161
  3. Capture before/after diff + bundle MD5 (before/after)
162
162
  4. If any gate fails → rollback the change (git checkout the file), log REJECTED in APPLY-LOG, continue with next item
163
163
  5. If passes → log APPLIED in APPLY-LOG with file:line
@@ -173,7 +173,7 @@ verify: true
173
173
 
174
174
  **Phase 5 (new — Darwin ratchet).** The output of a distillation is NOT "has skill" — it's "target improved." Read `applied-changes.md` + the target project's git diff. For each APPLIED item, re-verify:
175
175
  - Does the change actually improve the target? (concrete, measurable — e.g. "new helper reduces 18 sites to 1 import"; not vibes)
176
- - Do the tests still pass? (`npm run test:critical`)
176
+ - Do the tests still pass? (`npm run test:critical` — or the target's fastest documented check; never the full suite per item)
177
177
  - Did the bundle MD5 change as expected? (intentional change OK; unexpected change → flag)
178
178
  - Is the change consistent with target's existing style/conventions? (no foreign code injected)
179
179
 
@@ -21,4 +21,4 @@ dependsOn: execute
21
21
  verify: true
22
22
 
23
23
  Verify the fix with available evidence.
24
- Run FAST checks ONCE (cache output to .crew/cache/): `npm run test:critical && npx tsc --noEmit` (completes in <60s). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the fix. Do NOT re-run tests. Give PASS or FAIL with specific test evidence.
24
+ Run FAST checks ONCE and cache output WITH provenance to `.crew/cache/` (per `agents/verifier.md`: unique per-attempt filename, `set -o pipefail` so the recorded exit code is the command's — not `tee`'s — and a sibling `.meta` recording command, git revision, tree fingerprint): `npm run test:critical && npx tsc --noEmit` (completes in <60s; if this repo lacks these scripts, use its fastest documented check instead). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the fix — reuse a cached log only when its provenance matches. Do NOT re-run tests. Give PASS or FAIL with specific test evidence.
@@ -27,4 +27,4 @@ dependsOn: execute
27
27
  verify: true
28
28
 
29
29
  Verify completion for: {goal}
30
- Run FAST checks ONCE (cache output to .crew/cache/): `npm run test:critical && npx tsc --noEmit` (completes in <60s). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the changes. Do NOT re-run tests. Give PASS or FAIL with specific test evidence.
30
+ Run FAST checks ONCE and cache output WITH provenance to `.crew/cache/` (per `agents/verifier.md`: unique per-attempt filename, `set -o pipefail` so the recorded exit code is the command's — not `tee`'s — and a sibling `.meta` recording command, git revision, tree fingerprint): `npm run test:critical && npx tsc --noEmit` (completes in <60s; if this repo lacks these scripts, use its fastest documented check instead). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the changes — reuse a cached log only when its provenance matches. Do NOT re-run tests. Give PASS or FAIL with specific test evidence.
@@ -28,4 +28,4 @@ role: verifier
28
28
  dependsOn: code-review, security-review
29
29
  verify: true
30
30
 
31
- Run FAST checks ONCE (cache output to .crew/cache/): `npm run test:critical && npx tsc --noEmit` (completes in <60s). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with reviewer and security-reviewer findings. Confirm each finding against real test output. Give PASS if findings match evidence, FAIL if critical findings are false positives or tests reveal new issues.
31
+ Run FAST checks ONCE and cache output WITH provenance to `.crew/cache/` (per `agents/verifier.md`: unique per-attempt filename, `set -o pipefail` so the recorded exit code is the command's — not `tee`'s — and a sibling `.meta` recording command, git revision, tree fingerprint): `npm run test:critical && npx tsc --noEmit` (completes in <60s; if this repo lacks these scripts, use its fastest documented check instead). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with reviewer and security-reviewer findings — reuse a cached log only when its provenance matches. Confirm each finding against real test output. Give PASS if findings match evidence, FAIL if critical findings are false positives or tests reveal new issues.
@@ -23,4 +23,4 @@ dependsOn: execute
23
23
  verify: true
24
24
 
25
25
  Verify the fix with available evidence, AND cross-reference the SPEC-EVIDENCE footer from explore+execute against the spec's requirements (machine-check the citations vs the spec id set).
26
- Run FAST checks ONCE (cache output to .crew/cache/): `npm run test:critical && npx tsc --noEmit` (completes in <60s). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the fix. Do NOT re-run tests. Give PASS or FAIL with specific test evidence + spec gate verdict.
26
+ Run FAST checks ONCE and cache output WITH provenance to `.crew/cache/` (per `agents/verifier.md`: unique per-attempt filename, `set -o pipefail` so the recorded exit code is the command's — not `tee`'s — and a sibling `.meta` recording command, git revision, tree fingerprint): `npm run test:critical && npx tsc --noEmit` (completes in <60s; if this repo lacks these scripts, use its fastest documented check instead). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the fix — reuse a cached log only when its provenance matches. Do NOT re-run tests. Give PASS or FAIL with specific test evidence + spec gate verdict.
@@ -1,208 +0,0 @@
1
- # pi-crew Migration Guide: v0.4 → v0.5
2
-
3
- **Author:** pi-crew team
4
- **Date:** 2026-06-01
5
- **Version:** 0.5.5 *(covers v0.4→v0.5 migration; later v0.5.x versions are drop-in replacements)*
6
-
7
- ---
8
-
9
- ## Overview
10
-
11
- This guide covers breaking changes and new features introduced in v0.5.x.
12
-
13
- ---
14
-
15
- ## v0.5.5 Highlights (June 2026)
16
-
17
- v0.5.5 closes 13 rounds of code review. The user-facing changes are:
18
-
19
- - **Linear-time secret redaction** at all event/mailbox/artifact boundaries.
20
- - **v8.deserialize hardened** with `BINARY_MAGIC` headers — old binaries are auto-discarded.
21
- - **Adaptive implementation workflow** now has a single `assess` step; the planner picks the smallest effective crew.
22
- - **Async-notifier debounce** of 30 s — completion notifications can be delayed by up to 30 s.
23
- - **Mailbox delivery state capped at 10000 messages** — older entries are pruned FIFO.
24
- - **Anchors cap at 50 with 100 handoffs each** — older handoffs are pruned FIFO.
25
-
26
- No new public API is required for any of these changes. If you pinned a `BINARY_MAGIC`-guarded binary from a previous session, delete `~/.pi/agent/pi-crew/.cache/active-run-index.bin` once.
27
-
28
- ## v0.5.4 → v0.5.5 Migration
29
-
30
- No breaking changes. Drop-in replacement.
31
-
32
- ## Breaking Changes
33
-
34
- ### 1. Environment Variable Allowlist (Security)
35
-
36
- **Before (v0.4):**
37
- ```typescript
38
- // Child Pi workers received ALL matching secrets
39
- "*_API_KEY",
40
- "*_TOKEN",
41
- "*_SECRET",
42
- ```
43
-
44
- **After (v0.5):**
45
- ```typescript
46
- // Only explicit provider keys
47
- "ANTHROPIC_API_KEY",
48
- "OPENAI_API_KEY",
49
- "GOOGLE_API_KEY",
50
- // ...
51
- ```
52
-
53
- **Action Required:** If your workflows rely on custom environment variables with `*_API_KEY` patterns, you must now explicitly list them:
54
- ```json
55
- {
56
- "piCrew": {
57
- "runtime": {
58
- "envAllowlist": ["MY_CUSTOM_API_KEY", "MY_OTHER_KEY"]
59
- }
60
- }
61
- }
62
- ```
63
-
64
- ---
65
-
66
- ### 2. Mock Mode Requires Dual Environment Variables
67
-
68
- **Before (v0.4):**
69
- ```bash
70
- PI_TEAMS_MOCK_CHILD_PI=success # Works silently
71
- ```
72
-
73
- **After (v0.5):**
74
- ```bash
75
- PI_TEAMS_MOCK_CHILD_PI=success
76
- PI_CREW_ALLOW_MOCK=1 # Required for security
77
- ```
78
-
79
- **Action Required:** Update CI/CD and test scripts that use mock mode.
80
-
81
- ---
82
-
83
- ### 3. Skill Frontmatter Format
84
-
85
- **Before (v0.4):**
86
- ```yaml
87
- ---
88
- name: my-skill
89
- description: "My skill description"
90
- ---
91
- ```
92
-
93
- **After (v0.5):**
94
- ```yaml
95
- ---
96
- name: my-skill
97
- description: "My skill description"
98
- triggers:
99
- - "trigger phrase 1"
100
- - "trigger phrase 2"
101
- ---
102
- ```
103
-
104
- **Action Required:** Run `node scripts/check-all-skills.ts` to identify skills needing `triggers` field.
105
-
106
- ---
107
-
108
- ## New Features in v0.5
109
-
110
- ### 1. Enhanced Security
111
-
112
- - **Secure env allowlist**: Only explicit API keys passed to child processes
113
- - **Mock mode protection**: Requires `PI_CREW_ALLOW_MOCK=1` alongside `PI_TEAMS_MOCK_CHILD_PI`
114
- - **Worktree hook hardening**: Safer execution on Windows
115
-
116
- ### 2. Improved Reliability
117
-
118
- - **Terminal event durability**: Critical events (task.completed, task.failed) now bypass event buffering
119
- - **Race condition fixes**: Foreground interrupt requests are now properly serialized
120
- - **File descriptor cleanup**: Background runner properly closes log file descriptors
121
-
122
- ### 3. Better Observability
123
-
124
- - **Reduced cache TTL**: Manifest cache now expires in 30s instead of 5min for faster state updates
125
- - **Decision ledger integrity**: Ledger entries are preserved during promote/decay operations
126
-
127
- ### 4. Skill System
128
-
129
- - **Standardized triggers**: All 35 built-in skills now have explicit trigger phrases
130
- - **Enforcement gates**: Skills include checklist-based enforcement sections
131
- - **Anti-patterns**: Most skills include anti-pattern documentation
132
-
133
- ---
134
-
135
- ## Configuration Changes
136
-
137
- ### New Config Keys
138
-
139
- | Key | Type | Default | Description |
140
- |-----|------|---------|-------------|
141
- | `limits.heartbeatStaleMs` | number | 30000 | Stale heartbeat threshold |
142
- | `runtime.effectivenessGuard` | string | "off" | Effectiveness guard level |
143
- | `runtime.completionMutationGuard` | string | "off" | Mutation guard level |
144
-
145
- ### Deprecated Config Keys
146
-
147
- None in v0.5.
148
-
149
- ---
150
-
151
- ## Workflow Migration
152
-
153
- ### Updating Custom Agents
154
-
155
- 1. Ensure agent files have `triggers` in frontmatter:
156
- ```yaml
157
- ---
158
- name: my-agent
159
- triggers:
160
- - "my trigger"
161
- ---
162
- ```
163
-
164
- 2. Verify agent is discovered:
165
- ```bash
166
- team action=list agent=my-agent
167
- ```
168
-
169
- ### Updating Custom Teams
170
-
171
- 1. Validate team config:
172
- ```bash
173
- team action=validate resource=team name=my-team
174
- ```
175
-
176
- 2. Check for breaking changes in role/task definitions.
177
-
178
- ---
179
-
180
- ## Testing Checklist
181
-
182
- After upgrading to v0.5:
183
-
184
- - [ ] Run `team action=doctor` to verify configuration
185
- - [ ] Run `node scripts/check-all-skills.ts` to verify skills
186
- - [ ] Test mock mode with both env vars set
187
- - [ ] Verify environment variables are properly filtered in child processes
188
- - [ ] Test foreground interrupt (cancel) behavior
189
- - [ ] Verify terminal events are properly logged
190
-
191
- ---
192
-
193
- ## Rollback
194
-
195
- If issues occur after upgrade:
196
-
197
- ```bash
198
- # Revert to v0.4.x
199
- pi install npm:pi-crew@0.4.x
200
- ```
201
-
202
- ---
203
-
204
- ## Support
205
-
206
- - **Issues**: https://github.com/baphuongna/pi-crew/issues
207
- - **Documentation**: [docs/](docs/)
208
- - **Changelog**: [CHANGELOG.md](CHANGELOG.md)