pi-crew 0.9.67 → 0.10.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.
Files changed (222) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/agents/analyst.md +1 -1
  3. package/agents/cold-verifier.md +3 -1
  4. package/agents/critic.md +1 -1
  5. package/agents/executor.md +1 -1
  6. package/agents/explorer.md +1 -1
  7. package/agents/planner.md +1 -1
  8. package/agents/reviewer.md +1 -1
  9. package/agents/security-reviewer.md +1 -1
  10. package/agents/test-engineer.md +1 -1
  11. package/agents/verifier.md +1 -1
  12. package/agents/writer.md +1 -1
  13. package/dist/index.mjs +65847 -60331
  14. package/docs/README.md +2 -0
  15. package/docs/actions-reference.md +31 -0
  16. package/docs/commands-reference.md +17 -6
  17. package/docs/resource-formats.md +13 -0
  18. package/package.json +9 -6
  19. package/schema.json +15 -1
  20. package/scripts/resource-sampler.mjs +36 -2
  21. package/skills/requirements-to-task-packet/SKILL.md +26 -0
  22. package/skills/widget-rendering/SKILL.md +7 -7
  23. package/src/agents/agent-config.ts +36 -14
  24. package/src/agents/discover-agents.ts +23 -14
  25. package/src/config/config-merge.ts +183 -0
  26. package/src/config/config-validation.ts +681 -0
  27. package/src/config/config.ts +22 -864
  28. package/src/config/defaults.ts +35 -1
  29. package/src/config/drift-detector.ts +1 -1
  30. package/src/config/env-vars.ts +691 -0
  31. package/src/config/role-tools.ts +8 -8
  32. package/src/config/sanitize-project-config.ts +172 -0
  33. package/src/config/types.ts +30 -0
  34. package/src/extension/async-notifier.ts +25 -2
  35. package/src/extension/crew-vibes/config.ts +2 -1
  36. package/src/extension/plan-orchestrate.ts +132 -0
  37. package/src/extension/registration/commands/dashboard.ts +158 -0
  38. package/src/extension/registration/commands/index.ts +35 -0
  39. package/src/extension/registration/commands/manage.ts +303 -0
  40. package/src/extension/registration/commands/run.ts +214 -0
  41. package/src/extension/registration/commands/shared.ts +631 -0
  42. package/src/extension/registration/commands/status.ts +60 -0
  43. package/src/extension/registration/commands.ts +13 -1224
  44. package/src/extension/registration/lifecycle-handlers.ts +126 -15
  45. package/src/extension/registration/subagent-tools.ts +218 -9
  46. package/src/extension/rpc-hmac.ts +5 -3
  47. package/src/extension/team-tool/api/heartbeat.ts +47 -10
  48. package/src/extension/team-tool/api/plan-approval.ts +9 -0
  49. package/src/extension/team-tool/api/task-claims.ts +109 -40
  50. package/src/extension/team-tool/cancel.ts +84 -50
  51. package/src/extension/team-tool/dispatch/index.ts +1 -0
  52. package/src/extension/team-tool/dispatch/run.ts +4 -1
  53. package/src/extension/team-tool/doctor.ts +103 -1
  54. package/src/extension/team-tool/orchestrate.ts +66 -1
  55. package/src/extension/team-tool/plans.ts +192 -0
  56. package/src/extension/team-tool/respond.ts +197 -65
  57. package/src/extension/team-tool/run-deadline.ts +16 -1
  58. package/src/extension/team-tool/run-intent.ts +63 -0
  59. package/src/extension/team-tool/run.ts +48 -18
  60. package/src/extension/team-tool/status.ts +84 -26
  61. package/src/extension/team-tool.ts +11 -2
  62. package/src/hooks/registry.ts +1 -6
  63. package/src/i18n.ts +9 -0
  64. package/src/prompt/prompt-runtime.ts +521 -2
  65. package/src/prompt/worker-events-channel.ts +173 -0
  66. package/src/runtime/README.md +8 -8
  67. package/src/runtime/async-runner.ts +7 -3
  68. package/src/runtime/background-runner.ts +42 -14
  69. package/src/runtime/broker/broker-issuer.ts +9 -2
  70. package/src/runtime/broker/crew-broker-tokens.ts +43 -6
  71. package/src/runtime/broker/crew-broker.ts +838 -10
  72. package/src/runtime/broker/wait-status-cache.ts +157 -0
  73. package/src/runtime/budget-enforcement.ts +281 -0
  74. package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
  75. package/src/runtime/child-pi/child-pi-timers.ts +324 -0
  76. package/src/runtime/child-pi/child-pi.ts +97 -201
  77. package/src/runtime/child-pi/mock-fixtures.ts +16 -2
  78. package/src/runtime/crew-agent-records.ts +259 -14
  79. package/src/runtime/delegate-spawn.ts +148 -0
  80. package/src/runtime/deterministic-ast.ts +2 -1
  81. package/src/runtime/dispatch-batch.ts +945 -0
  82. package/src/runtime/finalize-run.ts +557 -0
  83. package/src/runtime/goal-workflow/adaptive-plan.ts +87 -13
  84. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +3 -2
  85. package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
  86. package/src/runtime/group-join.ts +11 -125
  87. package/src/runtime/live-session/live-session-runtime.ts +26 -1
  88. package/src/runtime/merge-gate.ts +7 -1
  89. package/src/runtime/merge-loop.ts +130 -0
  90. package/src/runtime/model/model-budget-summary.ts +53 -0
  91. package/src/runtime/model/model-fallback.ts +32 -2
  92. package/src/runtime/model/pi-args.ts +10 -0
  93. package/src/runtime/model/provider-extensions.ts +10 -0
  94. package/src/runtime/orphan-worker-registry.ts +1 -1
  95. package/src/runtime/output/output-validator.ts +45 -0
  96. package/src/runtime/parent-guard.ts +3 -1
  97. package/src/runtime/peer-dep.ts +2 -1
  98. package/src/runtime/per-write-validator.ts +0 -5
  99. package/src/runtime/pi-spawn.ts +61 -15
  100. package/src/runtime/plan-approval.ts +125 -0
  101. package/src/runtime/plan-replan.ts +151 -0
  102. package/src/runtime/recovery/checkpoint.ts +0 -18
  103. package/src/runtime/recovery/crash-recovery.ts +95 -46
  104. package/src/runtime/scheduler-context.ts +98 -0
  105. package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
  106. package/src/runtime/scheduling/global-worker-cap.ts +2 -1
  107. package/src/runtime/scheduling/nested-slots.ts +70 -0
  108. package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
  109. package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
  110. package/src/runtime/scratchpad/README.md +10 -5
  111. package/src/runtime/scratchpad/guest.ts +103 -2
  112. package/src/runtime/scratchpad/protocol.ts +13 -1
  113. package/src/runtime/scratchpad/transform.ts +206 -12
  114. package/src/runtime/settings-store.ts +219 -0
  115. package/src/runtime/spawn-policy.ts +217 -0
  116. package/src/runtime/stale-reconciler.ts +87 -6
  117. package/src/runtime/subagent-manager.ts +25 -1
  118. package/src/runtime/task-output-context.ts +230 -9
  119. package/src/runtime/task-packet.ts +23 -1
  120. package/src/runtime/task-runner/child-executor.ts +106 -7
  121. package/src/runtime/task-runner/post-execution.ts +125 -1
  122. package/src/runtime/task-runner/pre-execution.ts +39 -1
  123. package/src/runtime/task-runner/prompt-builder.ts +51 -1
  124. package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
  125. package/src/runtime/task-runner/spec-evidence.ts +403 -0
  126. package/src/runtime/task-runner/state-helpers.ts +26 -24
  127. package/src/runtime/task-runner.ts +11 -0
  128. package/src/runtime/team-runner.ts +129 -1671
  129. package/src/runtime/verification/spec-sandbox.ts +255 -0
  130. package/src/runtime/verification/verification-gates.ts +3 -2
  131. package/src/runtime/verification/verification-worktree.ts +2 -1
  132. package/src/runtime/workflow-phase-advance.ts +100 -0
  133. package/src/runtime/workspace-tree.ts +9 -0
  134. package/src/schema/config-schema.ts +65 -25
  135. package/src/schema/sensitive-config-paths.ts +64 -0
  136. package/src/schema/team-tool-schema.ts +13 -3
  137. package/src/state/README.md +4 -10
  138. package/src/state/atomic-write.ts +20 -3
  139. package/src/state/contracts.ts +38 -0
  140. package/src/state/coordination/mailbox.ts +12 -2
  141. package/src/state/event-log/cursor.ts +223 -0
  142. package/src/state/event-log/event-log-rotation.ts +12 -4
  143. package/src/state/event-log/event-log.ts +152 -369
  144. package/src/state/event-log/sequence-cache.ts +373 -0
  145. package/src/state/event-log/worker-atomic-writer.ts +2 -1
  146. package/src/state/stores/active-run-registry.ts +3 -2
  147. package/src/state/stores/manifest-io.ts +237 -0
  148. package/src/state/stores/ownership-map.ts +162 -0
  149. package/src/state/stores/plan-store.ts +241 -0
  150. package/src/state/stores/run-cache.ts +0 -90
  151. package/src/state/stores/spec-store.ts +189 -0
  152. package/src/state/stores/state-store.ts +66 -229
  153. package/src/state/types.ts +197 -0
  154. package/src/ui/dashboard-panes/plan-pane.ts +136 -0
  155. package/src/ui/dashboard-panes/progress-pane.ts +6 -0
  156. package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
  157. package/src/ui/heartbeat-aggregator.ts +9 -1
  158. package/src/ui/keybinding-map.ts +54 -13
  159. package/src/ui/powerbar-publisher.ts +52 -1
  160. package/src/ui/run-dashboard.ts +31 -5
  161. package/src/ui/run-snapshot-cache.ts +57 -30
  162. package/src/ui/snapshot-types.ts +6 -1
  163. package/src/ui/widget/widget-renderer.ts +9 -1
  164. package/src/utils/file-coalescer.ts +0 -4
  165. package/src/utils/fs-errno.ts +66 -0
  166. package/src/utils/fs-watch.ts +1 -1
  167. package/src/utils/internal-error.ts +3 -1
  168. package/src/utils/paths.ts +11 -3
  169. package/src/utils/task-name-generator.ts +1 -8
  170. package/src/workflows/discover-workflows.ts +19 -2
  171. package/src/workflows/validate-workflow.ts +7 -1
  172. package/src/workflows/workflow-config.ts +10 -0
  173. package/src/workflows/workflow-serializer.ts +3 -0
  174. package/src/worktree/worktree-manager.ts +22 -0
  175. package/src/agents/agent-search.ts +0 -98
  176. package/src/benchmark/benchmark-runner.ts +0 -313
  177. package/src/benchmark/feedback-loop.ts +0 -73
  178. package/src/config/resilient-parser.ts +0 -117
  179. package/src/extension/crew-vibes/cat-frames.ts +0 -18
  180. package/src/extension/result-watcher.ts +0 -139
  181. package/src/observability/exporters/prometheus-exporter.ts +0 -54
  182. package/src/observability/metric-retention.ts +0 -64
  183. package/src/runtime/compaction/compaction-summary.ts +0 -278
  184. package/src/runtime/errors/crew-errors.ts +0 -162
  185. package/src/runtime/live-session/intercom-bridge.ts +0 -187
  186. package/src/runtime/loop-gates.ts +0 -128
  187. package/src/runtime/metric-parser.ts +0 -36
  188. package/src/runtime/output/stream-preview.ts +0 -184
  189. package/src/runtime/output/tool-progress.ts +0 -278
  190. package/src/runtime/phase-tracker.ts +0 -385
  191. package/src/runtime/pipeline-runner.ts +0 -523
  192. package/src/runtime/process/process-lifecycle.ts +0 -491
  193. package/src/runtime/recovery/retry-runner.ts +0 -330
  194. package/src/runtime/run-drift.ts +0 -219
  195. package/src/runtime/scratchpad/snapshot-hmac.ts +0 -167
  196. package/src/runtime/task-quality.ts +0 -199
  197. package/src/runtime/task-runner/run-projection.ts +0 -128
  198. package/src/runtime/verification/post-checks.ts +0 -142
  199. package/src/state/coordination/schedule.ts +0 -166
  200. package/src/state/event-log/jsonl-writer.ts +0 -115
  201. package/src/state/hook-instinct-bridge.ts +0 -94
  202. package/src/state/hook-integrations.ts +0 -51
  203. package/src/state/session-state-map.ts +0 -51
  204. package/src/state/stores/blob-store.ts +0 -308
  205. package/src/state/stores/instinct-store.ts +0 -275
  206. package/src/state/stores/observation-store.ts +0 -176
  207. package/src/state/tiered-eval.ts +0 -480
  208. package/src/state/types-eval.ts +0 -58
  209. package/src/tools/safe-bash-extension.ts +0 -54
  210. package/src/tools/safe-bash.ts +0 -505
  211. package/src/ui/agent-management-overlay.ts +0 -160
  212. package/src/ui/crew-footer.ts +0 -102
  213. package/src/ui/crew-select-list.ts +0 -114
  214. package/src/ui/dashboard-panes/capability-pane.ts +0 -77
  215. package/src/ui/transcript-entries.ts +0 -256
  216. package/src/utils/conflict-detect.ts +0 -721
  217. package/src/utils/fingerprint.ts +0 -180
  218. package/src/utils/gh-protocol.ts +0 -556
  219. package/src/utils/project-detector.ts +0 -160
  220. package/src/utils/sse-parser.ts +0 -131
  221. package/src/workflows/cost-estimator.ts +0 -34
  222. package/src/workflows/intermediate-store.ts +0 -166
package/docs/README.md CHANGED
@@ -30,6 +30,8 @@ stay at the root of this directory; historical/retired docs live in
30
30
  | [`COVERAGE-ASSESSMENT-2026-08-01.md`](./COVERAGE-ASSESSMENT-2026-08-01.md) | v0.9.56 coverage assessment + source-reorg history |
31
31
  | [`AUDIT-2026-07-30.md`](./AUDIT-2026-07-30.md) | v0.9.56 comprehensive audit (6 parallel streams) |
32
32
  | [`REMEDIATION-PLAN-2026-07-30.md`](./REMEDIATION-PLAN-2026-07-30.md) | v0.9.56 remediation plan |
33
+ | [`refactor-plan.md`](./refactor-plan.md) | Maintainability refactor plan (phased, v0.9.68) |
34
+ | [`refactor-plan.review.md`](./refactor-plan.review.md) | Verification sweep 3 (vòng 4) audit trail — 3 large-file baselines CONFIRMED, 6 corrections, Phase 4 reframe |
33
35
 
34
36
  ## Subdirectories
35
37
 
@@ -2,6 +2,23 @@
2
2
 
3
3
  The `team` tool is the primary tool that pi-crew registers with Pi. All operations go through `action`.
4
4
 
5
+ ### `plans` — Inspect and decide a run's plan record (T2/R4)
6
+
7
+ Reads the versioned `PlanRecord` list at `<stateRoot>/plans/plans.json` (ADR-4). Pre-v2 runs without a record degrade gracefully with the manifest-gate info.
8
+
9
+ ```json
10
+ { "action": "plans", "runId": "team_..." }
11
+ { "action": "plans", "runId": "team_...", "config": { "subAction": "get", "rev": 2 } }
12
+ { "action": "plans", "runId": "team_...", "config": { "subAction": "list" } }
13
+ { "action": "plans", "runId": "team_...", "config": { "subAction": "diff", "a": 1, "b": 2 } }
14
+ { "action": "plans", "runId": "team_...", "config": { "subAction": "approve" } }
15
+ ```
16
+
17
+ - `get` (default) renders the current (or pinned `rev`) revision with derived per-item progress (done/failed/running from linked `taskIds`).
18
+ - `list` renders the revision history.
19
+ - `diff` reports item-level changes between two revisions (added / removed / dropped / status changes / retitles).
20
+ - `approve` / `reject` delegate to the api `approve-plan` / `cancel-plan` ops (dual-write: manifest keeps its vocabulary — `cancelled` on deny — the record side uses `rejected`).
21
+
5
22
  ## Quick Reference
6
23
 
7
24
  | Action | Purpose | When to use |
@@ -10,6 +27,7 @@ The `team` tool is the primary tool that pi-crew registers with Pi. All operatio
10
27
  | `run` | Create a run and execute a workflow | Main operation |
11
28
  | `parallel` | Fan out independent tasks as concurrent runs | When you need N truly concurrent agents |
12
29
  | `plan` | Preview a workflow without running tasks | Dry-run planning |
30
+ | `plans` | Inspect/decide the versioned plan record of a run | Plan revisions, diff, approve/reject |
13
31
  | `orchestrate` | Execute from a plan document | Automate a plan |
14
32
  | `schedule` | Schedule recurring runs | Periodic automation |
15
33
  | `scheduled` | List scheduled jobs | View schedules |
@@ -800,6 +818,19 @@ Returns `status: "error"` when the run finished as `failed`.
800
818
  > ⚠️ **Internal / experimental.** Queues a steering message that is delivered to
801
819
  > a task's worker session on its next turn. The message is appended to
802
820
  > `pendingSteers` and also written to a live steering file for immediate delivery.
821
+ >
822
+ > **Updated (T1/WP-1, 2026-08-17, v0.10.1):** the agent-level tools
823
+ > `steer_subagent` / `crew_agent_steer` are now **implemented** — they resolve the
824
+ > subagent record via the unified ownership map (`src/state/stores/ownership-map.ts`,
825
+ > task ⇄ subagent ⇄ pid ⇄ artifacts) to the run's steering file and append a
826
+ > `{type:"steer", message, ts}` line that the worker polls every 500ms and picks
827
+ > up at its next turn boundary. Terminal-task refusal + size-cap parity with
828
+ > `team action=steer`; the steering file is truncated at each worker spawn so
829
+ > lines are scoped to one worker incarnation. The record's `taskId` is linked at
830
+ > dispatch time (onRunStarted), so mid-run steering of a live one-shot agent
831
+ > works. See ADR `docs/decisions/2026-08-14-agent-steer-tools-planned-not-implemented.md`
832
+ > (status: CLOSED). `team action=steer` (runId + taskId + message) remains the
833
+ > run-level path.
803
834
 
804
835
  ```json
805
836
  {
@@ -8,21 +8,22 @@ Slash commands are manual actions triggered from the Pi chat. Autonomous tool us
8
8
  |---------|-------|
9
9
  | `/teams` | List teams, agents, workflows, and recent runs |
10
10
  | `/team-run [options] <goal>` | Run a team workflow |
11
- | `/team-orchestrate <planPath>` | Execute from a plan document |
12
- | `/team-schedule [options]` | Schedule a recurring run |
13
- | `/team-scheduled` | List scheduled jobs |
14
11
  | `/team-cancel <runId>` | Cancel a run |
15
12
  | `/team-status <runId>` | View status |
16
13
  | `/team-summary <runId>` | View or write a summary |
17
14
  | `/team-resume <runId>` | Resume a stopped run |
18
- | `/team-search <query>` | BM25-ranked discovery |
19
- | `/team-graph <runId>` | Load, save, or list run graphs |
15
+ | `/team-retry <runId>` | Retry a failed run |
16
+ | `/team-result <runId>` | Show a task result artifact |
17
+ | `/team-respond <taskId> <message>` | Respond to a waiting worker task |
18
+ | `/team-follow-up [options]` | Manage follow-up notifications |
20
19
  | `/team-events <runId>` | View the event log |
21
20
  | `/team-artifacts <runId>` | View artifacts |
21
+ | `/team-transcript <runId>` | Open the transcript viewer |
22
22
  | `/team-worktrees <runId>` | View worktree metadata |
23
- | `/team-cleanup <runId>` | Remove worktrees |
23
+ | `/team-cleanup <runId>` | Remove worktrees (interactive menu: `/team-cleanup-menu`) |
24
24
  | `/team-forget <runId>` | Fully delete a run |
25
25
  | `/team-prune` | Delete multiple old runs |
26
+ | `/team-invalidate [scope]` | Invalidate caches |
26
27
  | `/team-export <runId>` | Export a run bundle |
27
28
  | `/team-import <path>` | Import a run bundle |
28
29
  | `/team-imports` | List imported bundles |
@@ -37,9 +38,16 @@ Slash commands are manual actions triggered from the Pi chat. Autonomous tool us
37
38
  | `/team-validate` | Validate resources |
38
39
  | `/team-help` | Help text |
39
40
  | `/team-doctor` | Diagnose the environment |
41
+ | `/team-mascot` | Toggle the mascot overlay |
40
42
  | **`/team-goal`** | **v0.9.0** Start autonomous goal loop (sub-actions: `start/status/pause/resume/stop/step/clear`) |
41
43
  | **`/workflows`** | **v0.9.0** List static + dynamic workflows (`.dwf.ts`) |
42
44
 
45
+ **Removed (v0.10.1 docs hygiene):** `/team-orchestrate`, `/team-schedule`,
46
+ `/team-scheduled`, `/team-search`, `/team-graph` — phantom entries; none of
47
+ these slash commands is registered. Their functionality is reachable via the
48
+ `team` tool actions (`action: "orchestrate" | "schedule" | "scheduled" |
49
+ "search" | "graph"`) instead.
50
+
43
51
  ---
44
52
 
45
53
  ## `/team-run` — Details
@@ -296,6 +304,9 @@ Creates directories:
296
304
  | `u` | View summary |
297
305
  | `i` | API read-manifest |
298
306
  | `q` or `Esc` | Close |
307
+ | `1`–`7` | Pane: agents / progress / mailbox / output / health / metrics / **plan** (v0.10.1, `PI_CREW_PLAN_UI=1`) |
308
+ | `A` / `n` | Plan approval approve/deny (progress + plan panes, while pending) |
309
+ | `X` | Plan revision diff (plan pane) |
299
310
 
300
311
  ---
301
312
 
@@ -107,6 +107,8 @@ Format:
107
107
  ---
108
108
  name: default
109
109
  description: Explore, plan, execute, verify
110
+ maxConcurrency: 3
111
+ specStrict: false
110
112
  ---
111
113
 
112
114
  ## explore
@@ -122,6 +124,15 @@ output: plan.md
122
124
  Create a plan for: {goal}
123
125
  ```
124
126
 
127
+ Frontmatter fields:
128
+
129
+ | Field | Meaning |
130
+ | --- | --- |
131
+ | `maxConcurrency` | Max parallel tasks for the run |
132
+ | `topology` | Explicit topology classification |
133
+ | `coalesceMicroTasks` | Coalesce micro-tasks sharing role+cwd into one worker prompt |
134
+ | `specStrict` | T4/R6 (ADR-6): strict spec mode — coverage AND machine-check; requires a `verifier` role step (reject-start) |
135
+
125
136
  Step fields:
126
137
 
127
138
  | Field | Meaning |
@@ -136,5 +147,7 @@ Step fields:
136
147
  | `progress` | `true`/`false` |
137
148
  | `worktree` | `true`/`false` metadata |
138
149
  | `verify` | `true`/`false` verification marker |
150
+ | `specRefs` | T4/R6 (ADR-6): workspace SpecRecord ids this step is held to (frozen into the packet at dispatch) |
151
+ | `specStrict` | Per-step strict override (`true`/`1`) |
139
152
 
140
153
  Each step starts with `## step-id` followed by recognized step metadata such as `role:` before the blank line. Level-2 headings inside task bodies are preserved unless they look like a step section with recognized metadata; use `###` or lower for maximum compatibility.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-crew",
3
- "version": "0.9.67",
3
+ "version": "0.10.1",
4
4
  "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
5
  "author": "baphuongna",
6
6
  "license": "MIT",
@@ -30,6 +30,7 @@
30
30
  ],
31
31
  "files": [
32
32
  "src/",
33
+ "!src/benchmark",
33
34
  "dist/index.mjs",
34
35
  "index.ts",
35
36
  "install.mjs",
@@ -67,12 +68,14 @@
67
68
  ],
68
69
  "scripts": {
69
70
  "check": "npm run ci",
70
- "ci": "npm run typecheck && npm run lint && npm run format:check && npm run check:conflict-markers && npm run check:decision-drift && npm run check:event-types && npm run check:lazy-imports && npm run check:bundle-staleness && npm run build:bundle && npm run check:bundle-size && npm run test:bundle && npm test && npm pack --dry-run",
71
+ "ci": "npm run check:lockfile-sync && npm run typecheck && npm run lint && npm run format:check && npm run check:conflict-markers && npm run check:decision-drift && npm run check:env-vars && npm run check:event-types && npm run check:lazy-imports && npm run check:bundle-staleness && npm run build:bundle && npm run check:bundle-size && npm run test:bundle && npm test && npm pack --dry-run",
72
+ "check:lockfile-sync": "node scripts/check-lockfile-sync.mjs",
71
73
  "check:lazy-imports": "node scripts/check-lazy-imports.mjs",
72
74
  "check:bundle-staleness": "node scripts/check-bundle-staleness.mjs",
73
75
  "check:bundle-size": "node scripts/check-bundle-size.mjs",
74
76
  "check:conflict-markers": "node scripts/check-conflict-markers.mjs",
75
77
  "check:decision-drift": "node scripts/check-decision-drift.mjs",
78
+ "check:env-vars": "node scripts/check-env-vars.mjs",
76
79
  "check:event-types": "node scripts/check-event-types-registry.mjs",
77
80
  "typecheck": "tsc --noEmit && node --experimental-strip-types -e \"await import('./index.ts'); console.log('strip-types import ok')\"",
78
81
  "lint": "biome check --linter-enabled=true --formatter-enabled=false --max-diagnostics=50 .",
@@ -136,10 +139,10 @@
136
139
  },
137
140
  "devDependencies": {
138
141
  "@biomejs/biome": "^2.5.3",
139
- "@earendil-works/pi-agent-core": "^0.83.0",
140
- "@earendil-works/pi-ai": "^0.83.0",
141
- "@earendil-works/pi-coding-agent": "^0.83.0",
142
- "@earendil-works/pi-tui": "^0.83.0",
142
+ "@earendil-works/pi-agent-core": "^0.84.0",
143
+ "@earendil-works/pi-ai": "^0.84.0",
144
+ "@earendil-works/pi-coding-agent": "^0.84.0",
145
+ "@earendil-works/pi-tui": "^0.84.0",
143
146
  "@types/node": "^25.9.5",
144
147
  "tsx": "^4.23.0",
145
148
  "typescript": "^7.0.2"
package/schema.json CHANGED
@@ -256,7 +256,21 @@
256
256
  "enabled": { "type": "boolean" },
257
257
  "pathHashLen": { "type": "integer", "minimum": 4, "maximum": 32 },
258
258
  "maxFrameBytes": { "type": "integer", "minimum": 1024, "maximum": 1048576 },
259
- "outboundQueueCap": { "type": "integer", "minimum": 32, "maximum": 4096 }
259
+ "outboundQueueCap": { "type": "integer", "minimum": 32, "maximum": 4096 },
260
+ "waitMethodsEnabled": {
261
+ "type": "boolean",
262
+ "description": "WP-2/R2 (ADR-0 2026-08-17-waiting-producer-ask item 7): gate for the broker wait.* methods. Default false - fail-closed."
263
+ }
264
+ }
265
+ },
266
+ "nesting": {
267
+ "type": "object",
268
+ "additionalProperties": false,
269
+ "description": "Governed nesting (ADR-5 docs/decisions/2026-08-17-governed-nesting.md). Worker-to-grandchild delegation via the delegate tool. Default false - fail-closed until WP-5 completes.",
270
+ "properties": {
271
+ "enabled": { "type": "boolean", "sensitive": true, "description": "Master switch. Privilege-raising: user config only; project-level nesting.enabled is ignored." },
272
+ "maxSlots": { "type": "integer", "minimum": 1, "maximum": 64, "description": "Nested-slot budget override. Default max(1, floor(globalSem/2))." },
273
+ "maxDepth": { "type": "integer", "minimum": 1, "maximum": 10, "description": "Max crew depth for delegate-driven nesting. Default 2 (mirrors PI_CREW_MAX_DEPTH clamp)." }
260
274
  }
261
275
  }
262
276
  }
@@ -147,9 +147,16 @@ function readProcStatus(pid) {
147
147
 
148
148
  // ---------- fallback via ps (non-Linux) ----------
149
149
  function readPs(pid) {
150
- const res = spawnSync("ps", ["-o", "rss=,pcpu=", "-p", String(pid)], { encoding: "utf8" });
150
+ // state= makes zombie detection possible on BSD ps (macOS): a dead child
151
+ // stays listed by ps until reaped — without the state check, watch-parent
152
+ // (R4) never sees the watched PID die and live-warn (proc_died) never fires
153
+ // (CI incident: resource-sampler-audit.test.ts on macos-latest). Linux ps
154
+ // also supports state=. Z/X (and empty) = dead.
155
+ const res = spawnSync("ps", ["-o", "rss=,pcpu=,state=", "-p", String(pid)], { encoding: "utf8" });
151
156
  if (res.status !== 0 || !res.stdout.trim()) return null;
152
157
  const parts = res.stdout.trim().split(/\s+/);
158
+ const state = parts.length >= 3 ? parts[2] : "";
159
+ if (state.startsWith("Z") || state.startsWith("X")) return null;
153
160
  return {
154
161
  pid,
155
162
  ppid: 0,
@@ -163,7 +170,34 @@ function readPs(pid) {
163
170
  // ---------- child discovery ----------
164
171
  function findDescendants(rootPid) {
165
172
  // BFS over /proc to find all PIDs whose ppid chain leads to rootPid.
166
- if (!existsSync("/proc")) return [rootPid];
173
+ // Non-Linux (macOS/BSD): no /proc — build the ppid map from `ps -eo pid=,ppid=`
174
+ // instead (R3 on macos-latest CI: /proc-less platforms previously returned
175
+ // [rootPid] only, so descendants were never sampled).
176
+ if (!existsSync("/proc")) {
177
+ try {
178
+ const res = spawnSync("ps", ["-eo", "pid=,ppid="], { encoding: "utf8" });
179
+ if (res.status !== 0 || !res.stdout.trim()) return [rootPid];
180
+ const ppidOf = new Map();
181
+ for (const line of res.stdout.trim().split("\n")) {
182
+ const parts = line.trim().split(/\s+/);
183
+ if (parts.length >= 2) ppidOf.set(Number.parseInt(parts[0], 10), Number.parseInt(parts[1], 10));
184
+ }
185
+ const result = new Set([rootPid]);
186
+ let grew = true;
187
+ while (grew) {
188
+ grew = false;
189
+ for (const [pid, ppid] of ppidOf) {
190
+ if (result.has(ppid) && !result.has(pid)) {
191
+ result.add(pid);
192
+ grew = true;
193
+ }
194
+ }
195
+ }
196
+ return [...result];
197
+ } catch {
198
+ return [rootPid];
199
+ }
200
+ }
167
201
  const all = [];
168
202
  try {
169
203
  for (const name of readdirSync("/proc")) {
@@ -62,6 +62,32 @@ Use observable checks:
62
62
  - compatibility requirements such as Windows paths or Pi CLI flags;
63
63
  - rollback notes.
64
64
 
65
+ ## Spec Pairs (pi-crew v0.10.1+, ADR-6)
66
+
67
+ When the target runs pi-crew, author the **SpecRecord + TaskPacket pair** instead of prose-only acceptance:
68
+
69
+ 1. **SpecRecord** (workspace `state/specs/<id>.json`): `requirements[]` with
70
+ `must|should|could` priority + stable ids; `acceptance[]` entries each tied to
71
+ a `requirementId`. Machine-checkable acceptances carry `command`,
72
+ `expectedDigest` (sha-256 hex of stdout) or `expectedExitCode`, and
73
+ `idempotent: true` — only idempotent commands are ever re-run.
74
+ 2. **PROVENANCE — critical**: specs you (an agent/skill path) write are persisted
75
+ `generated` and NEVER re-executed by the orchestrator. `manual`+`trusted`
76
+ (the only specs strict mode re-runs) are minted exclusively by USER-facing
77
+ import actions — a worker cannot author a command the root re-executes.
78
+ 3. **Wire the workflow**: add `specRefs: [<spec ids>]` to steps held to the spec;
79
+ `specStrict: true` in frontmatter opts the whole workflow into strict mode
80
+ (requires a `verifier` role step — the run rejects at start otherwise).
81
+ 4. **Executor footer contract**: workers must END results with
82
+
83
+ ```text
84
+ SPEC-EVIDENCE:
85
+ <acceptanceId>: <one-line evidence>
86
+ ```
87
+
88
+ Non-strict = mechanical coverage only (`unverified` badge on gaps, never
89
+ blocks). Strict = coverage AND machine-check; failures fail the run.
90
+
65
91
  ## Enforcement — Requirements to Task Packet Gate
66
92
 
67
93
  **Before dispatching workers, verify task packet has:**
@@ -32,16 +32,16 @@ In-memory map from `live-agent-manager.ts`. Provides:
32
32
 
33
33
  **When NOT used:** After `evictStaleLiveAgentHandles()` removes a handle, widget falls back to agent records on disk.
34
34
 
35
- ### 2. Snapshot cache (500ms TTL)
35
+ ### 2. Snapshot cache (1500ms TTL)
36
36
 
37
- `RunSnapshotCache` from `run-snapshot-cache.ts` caches parsed manifests and agents for 500ms. Reduces disk reads during rapid refresh.
37
+ `RunSnapshotCache` from `run-snapshot-cache.ts` caches parsed manifests and agents for 1500ms. Reduces disk reads during rapid refresh.
38
38
 
39
39
  **When used:** As the fallback when no live handle exists. Prevents excessive disk reads on every render tick.
40
40
 
41
41
  **Invalidation:** Cache is invalidated when:
42
42
  - `invalidate()` is called on a specific run
43
43
  - An empty result is returned (forces refresh on next tick)
44
- - TTL expires (500ms)
44
+ - TTL expires (1500ms)
45
45
 
46
46
  ### 3. `agents.json` on disk (durables, lowest priority)
47
47
 
@@ -156,7 +156,7 @@ Every render cycle (`renderTick` / `requestAnimationFrame`) must complete in <16
156
156
 
157
157
  ### TTL interactions
158
158
 
159
- - Snapshot cache TTL = 500ms
159
+ - Snapshot cache TTL = 1500ms
160
160
  - Preload interval must be < TTL to avoid render-time gaps
161
161
  - If preload interval ≥ TTL, the cache always has fresh data for render
162
162
 
@@ -248,7 +248,7 @@ If ANY answer is NO → Stop. Fix widget rendering issues before proceeding.
248
248
  ## Anti-patterns
249
249
 
250
250
  - **Blocking render with fs calls**: Every `readFileSync`, `readdirSync`, `fs.statSync` in the render path causes frame drops. Preload everything async.
251
- - **Stale cache in hot path**: If snapshot cache TTL is too long, widget shows outdated state. Keep TTL at 500ms or less.
251
+ - **Stale cache in hot path**: If snapshot cache TTL is too long, widget shows outdated state. Keep TTL at 1500ms or less (the code default; lower only with cause).
252
252
  - **No invalidation on empty**: When `readCrewAgents` returns `[]` (no agents yet), the cache must be invalidated on next tick to prevent showing empty for too long.
253
253
  - **Expired handles accumulating**: Without `evictStaleLiveAgentHandles`, the Map grows indefinitely. Call it on every refresh.
254
254
  - **Widget showing stale health warnings**: Completed/cancelled/failed runs should not show health warnings. Filter by status.
@@ -258,7 +258,7 @@ If ANY answer is NO → Stop. Fix widget rendering issues before proceeding.
258
258
  ## Source patterns
259
259
 
260
260
  - `src/ui/crew-widget.ts` — render, refresh, activeWidgetRuns, evictStaleLiveAgentHandles, agentActivity, describeLiveActivity
261
- - `src/ui/run-snapshot-cache.ts` — SnapshotCache, get, refreshIfStale, TTL=500ms
261
+ - `src/ui/run-snapshot-cache.ts` — SnapshotCache, get, refreshIfStale, TTL=1500ms
262
262
  - `src/runtime/crew-agent-records.ts` — readCrewAgents, agents.json
263
263
  - `src/runtime/process-status.ts` — hasStaleAsyncProcess, isDisplayActiveRun
264
264
  - `src/runtime/background-runner.ts` — active run filtering with async PID check
@@ -275,7 +275,7 @@ Render-path performance for the widget is non-negotiable. Treat every `render(wi
275
275
  - **Prefer `snapshotCache.get(runId)`** on render paths. If a synchronous fallback is genuinely unavoidable, classify it as first-load/rare and document why it can't be preloaded.
276
276
  - **Keep panes pure.** Dashboard panes must accept a snapshot/model and format strings only. Never call `fs.readFileSync`, `fs.readdirSync`, `fs.statSync`, network APIs, or large JSON parsing from pane render methods.
277
277
  - **Stay non-blocking at 60fps.** Each render cycle must complete in under ~16ms. Anything that can't finish synchronously (reads, fetches, directory scans) belongs in the async preload path, not `renderTick()`.
278
- - **Respect the snapshot-cache TTL of ≤500ms.** Keep the `RunSnapshotCache` TTL at 500ms or less so the widget never shows stale state. Watch TTL interactions: the preload interval must be shorter than the cache TTL, otherwise render-time refresh gaps appear.
278
+ - **Respect the snapshot-cache TTL of ≤1500ms.** Keep the `RunSnapshotCache` TTL at 1500ms or less (the code default; lower only with cause) so the widget never shows stale state. Watch TTL interactions: the preload interval must be shorter than the cache TTL, otherwise render-time refresh gaps appear.
279
279
  - **Guard session switches.** On a session switch, cancel timers and ensure in-flight async preloads cannot update a now-stale session's UI.
280
280
  - **Filter stale warnings by terminal status.** Do not surface health warnings for completed/failed/cancelled runs.
281
281
 
@@ -1,5 +1,6 @@
1
+ import { getCrewEnv } from "../config/env-vars.ts";
1
2
  import type { RoleToolConfig } from "../config/role-tools.ts";
2
- import { getToolConfig } from "../config/role-tools.ts";
3
+ import { getToolConfig, isScratchpadEnabledForRole } from "../config/role-tools.ts";
3
4
 
4
5
  /**
5
6
  * F1 (v0.7.9): canonical built-in tool name list. Used by `parseToolsField`
@@ -162,24 +163,45 @@ export function resolveToolPolicy(agent: AgentConfig, role?: string): ResolvedTo
162
163
  const roleConfig = role ? getToolConfig(role) : {};
163
164
  // allowlist: source-aware precedence (see doc above).
164
165
  const explicitTools = agent.source === "builtin" ? (roleConfig.tools ?? agent.tools) : (agent.tools ?? roleConfig.tools);
165
- // L5: when the agent opts into `loadMode: "lean"` AND provides a
166
- // non-empty `defaultTools` list, merge that list into the resolved
167
- // allowlist. The merge is additive (union, dedup, order-preserving) so
168
- // the existing source-aware precedence is preserved and a lean agent
169
- // gets a focused-but-non-empty tool set. Signal flow:
170
- // agent YAML frontmatter (loadMode, defaultTools)
171
- // → parsed into AgentConfig
172
- // → resolveToolPolicy(agent, role) here
173
- // → policy.tools returned to buildPiWorkerArgs
174
- // → `args.push("--tools", policy.tools.join(","))` in pi-args.ts
175
- // → child pi process sees the merged allowlist
176
- const tools =
166
+ let tools =
177
167
  agent.loadMode === "lean" && agent.defaultTools?.length ? uniqueToolMerge(explicitTools, agent.defaultTools) : explicitTools;
178
168
  // denylist: additive merge of role excludeTools + agent disallowedTools.
179
- const excludeTools = uniqueToolMerge(roleConfig.excludeTools, agent.disallowedTools);
169
+ let excludeTools = uniqueToolMerge(roleConfig.excludeTools, agent.disallowedTools);
170
+ // P2 (scratchpad adoption lever, rlm-deep-review-2026-08-12.md §5.1A):
171
+ // when scratchpad is armed for this role AND the operator opted in via
172
+ // PI_CREW_SCRATCHPAD_DEMOTE_BASH=1, remove `bash` from the tool surface so
173
+ // the model reaches for `sh()` inside scratchpad cells (structured value
174
+ // reuse) instead of `bash` (which always wins by default — the documented
175
+ // root cause of 0 scratchpad adoption). Gated behind a flag because it
176
+ // changes the tool surface the model sees (behavioral risk). The model
177
+ // keeps read/edit/write/ls/grep/find; shell ops must go via `sh()`.
178
+ if (shouldDemoteBashForScratchpad(role, agent)) {
179
+ tools = tools ? tools.filter((t) => t !== "bash") : tools;
180
+ excludeTools = uniqueToolMerge(excludeTools, ["bash"]);
181
+ }
180
182
  return { tools, excludeTools };
181
183
  }
182
184
 
185
+ /**
186
+ * P2: should `bash` be demoted (removed) for a scratchpad-armed role?
187
+ *
188
+ * True only when BOTH hold:
189
+ * (a) the role has scratchpad enabled (so the model still has a way to run
190
+ * shell commands — via the `sh()` binding inside scratchpad cells);
191
+ * (b) the operator opted in via `PI_CREW_SCRATCHPAD_DEMOTE_BASH=1`.
192
+ *
193
+ * Default off → zero behavior change (existing adoption stays at 0). On → the
194
+ * lever to break 0-adoption without full tool collapse. This is read-only
195
+ * config logic; the actual tool-surface change happens in `resolveToolPolicy`
196
+ * above and flows to BOTH spawn paths (child-pi `--tools`/`--exclude-tools` and
197
+ * live-session filterActiveTools) via the unified policy.
198
+ */
199
+ function shouldDemoteBashForScratchpad(role: string | undefined, agent: AgentConfig): boolean {
200
+ if (getCrewEnv("PI_CREW_SCRATCHPAD_DEMOTE_BASH") !== "1") return false;
201
+ if (!role) return false;
202
+ return isScratchpadEnabledForRole(role, { scratchpad: agent.scratchpad });
203
+ }
204
+
183
205
  /**
184
206
  * Build agent session options including role-based tool restrictions.
185
207
  * @param agent - The agent configuration
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { type LoadedPiTeamsConfig, loadConfig } from "../config/config.ts";
4
+ import { getCrewEnv } from "../config/env-vars.ts";
4
5
  import { discoverProviderExtensionPaths } from "../runtime/model/provider-extensions.ts";
5
6
  import { parseCsv, parseFrontmatter } from "../utils/frontmatter.ts";
6
7
  import { logInternalError } from "../utils/internal-error.ts";
@@ -136,19 +137,13 @@ function logSecurityEvent(event: SecurityEvent): void {
136
137
  }
137
138
 
138
139
  /**
139
- * Get recent security events (for debugging/testing).
140
+ * Get recent security events. Production reader: the team doctor report
141
+ * surfaces a compact summary (R7-13); tests assert on the full list.
140
142
  */
141
143
  export function getSecurityEventLog(): readonly SecurityEvent[] {
142
144
  return securityEventLog;
143
145
  }
144
146
 
145
- /**
146
- * Clear security event log (for testing).
147
- */
148
- export function clearSecurityEventLog(): void {
149
- securityEventLog.length = 0;
150
- }
151
-
152
147
  /**
153
148
  * Security check: throws if the agent name is protected.
154
149
  *
@@ -373,7 +368,11 @@ export function sanitizeAgentSystemPrompt(content: string, source: ResourceSourc
373
368
  * emitted the `contextMode: fork` warn-only notice. Avoids spam when
374
369
  * the discovery cache reloads or the same agent is parsed multiple
375
370
  * times in a session. Exported for test reset.
371
+ * R5-L5: FIFO cap so the set stays bounded in long-lived sessions
372
+ * (Set preserves insertion order; bounded naturally <50, cap is a
373
+ * safety net).
376
374
  */
375
+ const MAX_WARNED_FORK_AGENTS = 128;
377
376
  const warnedForkAgents = new Set<string>();
378
377
  export function __test_resetForkWarnings(): void {
379
378
  warnedForkAgents.clear();
@@ -406,14 +405,19 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
406
405
  // so the agent will behave as `fresh` regardless of the setting.
407
406
  // We warn (not throw) so existing configs that predate live-session
408
407
  // keep working. Deduped per-filePath so we don't spam on cache
409
- // reload. Use console.warn (no logger hook here yet; a future
410
- // refactor could pipe through the same log channel as
411
- // logInternalError below).
408
+ // reload. User-facing config notice (kept on console.warn deliberately
409
+ // so the user sees it in the interactive session; internal failures
410
+ // below use logInternalError).
412
411
  if (contextMode === "fork" && !warnedForkAgents.has(filePath)) {
413
412
  console.warn(
414
413
  "contextMode: 'fork' is only effective in live-session runtime; current default child-process will behave as 'fresh'. See docs/runtime-flow.md.",
415
414
  );
416
415
  warnedForkAgents.add(filePath);
416
+ // R5-L5: FIFO eviction — drop the oldest entry past the cap.
417
+ if (warnedForkAgents.size > MAX_WARNED_FORK_AGENTS) {
418
+ const oldest = warnedForkAgents.values().next().value;
419
+ if (oldest !== undefined) warnedForkAgents.delete(oldest);
420
+ }
417
421
  }
418
422
 
419
423
  return {
@@ -442,7 +446,7 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
442
446
  // code. Bypass only when PI_CREW_TRUST_PROJECT_AGENT_EXTENSIONS=1 is
443
447
  // explicitly set. buildPiWorkerArgs also enforces this as
444
448
  // defense-in-depth.
445
- ...((source === "project" || source === "project-pi") && process.env.PI_CREW_TRUST_PROJECT_AGENT_EXTENSIONS !== "1"
449
+ ...((source === "project" || source === "project-pi") && getCrewEnv("PI_CREW_TRUST_PROJECT_AGENT_EXTENSIONS") !== "1"
446
450
  ? { extensions: [], excludeExtensions: [] }
447
451
  : {
448
452
  extensions: frontmatter.extensions === "" ? [] : parseCsv(frontmatter.extensions),
@@ -494,7 +498,12 @@ function readAgentDir(dir: string, source: ResourceSource): AgentConfig[] {
494
498
  try {
495
499
  const stat = fs.statSync(fullPath);
496
500
  if (stat.size > MAX_AGENT_FILE_BYTES) {
497
- console.warn(`[pi-crew] Skipping oversized agent file (${stat.size} > ${MAX_AGENT_FILE_BYTES} bytes): ${fullPath}`);
501
+ logInternalError(
502
+ "discover-agents",
503
+ new Error(`Skipping oversized agent file (${stat.size} > ${MAX_AGENT_FILE_BYTES} bytes): ${fullPath}`),
504
+ undefined,
505
+ "warn",
506
+ );
498
507
  return undefined;
499
508
  }
500
509
  } catch {
@@ -536,7 +545,7 @@ function applyAgentOverrides(agents: AgentConfig[], cwd: string, loadedConfig?:
536
545
  // user-trusted config knob (provider extensions like pi-commandcode-provider)
537
546
  // and applies to builtin / user agents only.
538
547
  const isUntrustedProject = (agent: AgentConfig) =>
539
- (agent.source === "project" || agent.source === "project-pi") && process.env.PI_CREW_TRUST_PROJECT_AGENT_EXTENSIONS !== "1";
548
+ (agent.source === "project" || agent.source === "project-pi") && getCrewEnv("PI_CREW_TRUST_PROJECT_AGENT_EXTENSIONS") !== "1";
540
549
  const withGlobalExtensions = (agent: AgentConfig): AgentConfig => {
541
550
  if (isUntrustedProject(agent)) return agent;
542
551
  return deduped.length > 0 || agent.extensions !== undefined