pi-crew 0.9.68 → 0.10.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 (253) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/NOTICE.md +21 -0
  3. package/README.md +44 -2
  4. package/agents/analyst.md +1 -1
  5. package/agents/cold-verifier.md +3 -1
  6. package/agents/critic.md +1 -1
  7. package/agents/executor.md +1 -1
  8. package/agents/explorer.md +1 -1
  9. package/agents/planner.md +1 -1
  10. package/agents/reviewer.md +1 -1
  11. package/agents/security-reviewer.md +1 -1
  12. package/agents/test-engineer.md +1 -1
  13. package/agents/verifier.md +1 -1
  14. package/agents/writer.md +1 -1
  15. package/dist/index.mjs +68113 -60774
  16. package/docs/README.md +2 -0
  17. package/docs/actions-reference.md +31 -0
  18. package/docs/commands-reference.md +17 -6
  19. package/docs/resource-formats.md +13 -0
  20. package/package.json +4 -2
  21. package/schema.json +503 -91
  22. package/scripts/resource-sampler.mjs +36 -2
  23. package/skills/requirements-to-task-packet/SKILL.md +26 -0
  24. package/skills/widget-rendering/SKILL.md +7 -7
  25. package/src/agents/agent-config.ts +2 -1
  26. package/src/agents/discover-agents.ts +23 -14
  27. package/src/config/config-merge.ts +183 -0
  28. package/src/config/config-validation.ts +687 -0
  29. package/src/config/config.ts +22 -864
  30. package/src/config/defaults.ts +43 -2
  31. package/src/config/drift-detector.ts +1 -1
  32. package/src/config/env-vars.ts +691 -0
  33. package/src/config/role-tools.ts +11 -9
  34. package/src/config/sanitize-project-config.ts +172 -0
  35. package/src/config/types.ts +49 -1
  36. package/src/extension/async-notifier.ts +25 -2
  37. package/src/extension/crew-cleanup.ts +13 -0
  38. package/src/extension/crew-vibes/config.ts +2 -1
  39. package/src/extension/crew-vibes/footer.ts +19 -0
  40. package/src/extension/crew-vibes/index.ts +11 -1
  41. package/src/extension/plan-orchestrate.ts +132 -0
  42. package/src/extension/register.ts +8 -0
  43. package/src/extension/registration/command-registration.ts +1 -0
  44. package/src/extension/registration/commands/dashboard.ts +158 -0
  45. package/src/extension/registration/commands/index.ts +35 -0
  46. package/src/extension/registration/commands/manage.ts +303 -0
  47. package/src/extension/registration/commands/run.ts +228 -0
  48. package/src/extension/registration/commands/shared.ts +639 -0
  49. package/src/extension/registration/commands/status.ts +60 -0
  50. package/src/extension/registration/commands.ts +13 -1224
  51. package/src/extension/registration/foreground-run-controller.ts +10 -2
  52. package/src/extension/registration/lifecycle-handlers.ts +178 -17
  53. package/src/extension/registration/runtime-cleanup.ts +23 -5
  54. package/src/extension/registration/subagent-tools.ts +218 -9
  55. package/src/extension/registration/team-tool.ts +5 -1
  56. package/src/extension/registration/ui.ts +5 -4
  57. package/src/extension/rpc-hmac.ts +5 -3
  58. package/src/extension/team-tool/api/heartbeat.ts +47 -10
  59. package/src/extension/team-tool/api/plan-approval.ts +9 -0
  60. package/src/extension/team-tool/api/task-claims.ts +109 -40
  61. package/src/extension/team-tool/cancel.ts +84 -50
  62. package/src/extension/team-tool/dispatch/index.ts +1 -0
  63. package/src/extension/team-tool/dispatch/run.ts +4 -1
  64. package/src/extension/team-tool/doctor.ts +103 -1
  65. package/src/extension/team-tool/orchestrate.ts +66 -1
  66. package/src/extension/team-tool/plans.ts +192 -0
  67. package/src/extension/team-tool/respond.ts +197 -65
  68. package/src/extension/team-tool/run-deadline.ts +35 -3
  69. package/src/extension/team-tool/run-intent.ts +63 -0
  70. package/src/extension/team-tool/run.ts +74 -20
  71. package/src/extension/team-tool/status.ts +84 -26
  72. package/src/extension/team-tool.ts +11 -2
  73. package/src/hooks/registry.ts +1 -6
  74. package/src/i18n.ts +9 -0
  75. package/src/prompt/prompt-runtime.ts +521 -2
  76. package/src/prompt/worker-events-channel.ts +173 -0
  77. package/src/runtime/README.md +8 -8
  78. package/src/runtime/async-runner.ts +7 -3
  79. package/src/runtime/background-runner.ts +42 -14
  80. package/src/runtime/broker/broker-issuer.ts +9 -2
  81. package/src/runtime/broker/crew-broker-tokens.ts +43 -6
  82. package/src/runtime/broker/crew-broker.ts +838 -10
  83. package/src/runtime/broker/wait-status-cache.ts +157 -0
  84. package/src/runtime/budget-enforcement.ts +281 -0
  85. package/src/runtime/child-pi/child-pi-constants.ts +8 -0
  86. package/src/runtime/child-pi/child-pi-spawn.ts +60 -14
  87. package/src/runtime/child-pi/child-pi-streams.ts +21 -1
  88. package/src/runtime/child-pi/child-pi-timers.ts +324 -0
  89. package/src/runtime/child-pi/child-pi.ts +97 -201
  90. package/src/runtime/child-pi/mock-fixtures.ts +16 -2
  91. package/src/runtime/crew-agent-records.ts +259 -14
  92. package/src/runtime/delegate-spawn.ts +148 -0
  93. package/src/runtime/detached-run-results.ts +90 -0
  94. package/src/runtime/deterministic-ast.ts +2 -1
  95. package/src/runtime/dispatch-batch.ts +945 -0
  96. package/src/runtime/finalize-run.ts +557 -0
  97. package/src/runtime/goal-workflow/adaptive-plan.ts +116 -15
  98. package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +8 -3
  99. package/src/runtime/goal-workflow/goal-state-store.ts +1 -1
  100. package/src/runtime/group-join.ts +11 -125
  101. package/src/runtime/live-session/live-session-runtime.ts +26 -1
  102. package/src/runtime/merge-gate.ts +32 -10
  103. package/src/runtime/merge-loop.ts +130 -0
  104. package/src/runtime/model/model-budget-summary.ts +53 -0
  105. package/src/runtime/model/model-fallback.ts +36 -2
  106. package/src/runtime/model/pi-args.ts +10 -0
  107. package/src/runtime/model/provider-extensions.ts +10 -0
  108. package/src/runtime/orphan-worker-registry.ts +1 -1
  109. package/src/runtime/output/output-validator.ts +45 -0
  110. package/src/runtime/parent-guard.ts +3 -1
  111. package/src/runtime/peer-dep.ts +2 -1
  112. package/src/runtime/per-write-validator.ts +0 -5
  113. package/src/runtime/pi-spawn.ts +61 -15
  114. package/src/runtime/plan-approval.ts +125 -0
  115. package/src/runtime/plan-replan.ts +151 -0
  116. package/src/runtime/process-status.ts +16 -1
  117. package/src/runtime/recovery/checkpoint.ts +0 -18
  118. package/src/runtime/recovery/crash-recovery.ts +111 -46
  119. package/src/runtime/run-tracker.ts +77 -10
  120. package/src/runtime/scheduler-context.ts +98 -0
  121. package/src/runtime/scheduling/coalesce-tasks.ts +5 -0
  122. package/src/runtime/scheduling/global-worker-cap.ts +2 -1
  123. package/src/runtime/scheduling/nested-slots.ts +70 -0
  124. package/src/runtime/scheduling/run-coalesced-task-group.ts +64 -13
  125. package/src/runtime/scheduling/task-graph-scheduler.ts +0 -10
  126. package/src/runtime/settings-store.ts +219 -0
  127. package/src/runtime/spawn-policy.ts +217 -0
  128. package/src/runtime/stale-reconciler.ts +87 -6
  129. package/src/runtime/subagent-manager.ts +25 -1
  130. package/src/runtime/task-output-context.ts +230 -9
  131. package/src/runtime/task-packet.ts +23 -1
  132. package/src/runtime/task-runner/child-executor.ts +106 -7
  133. package/src/runtime/task-runner/post-execution.ts +125 -1
  134. package/src/runtime/task-runner/pre-execution.ts +39 -1
  135. package/src/runtime/task-runner/prompt-builder.ts +51 -1
  136. package/src/runtime/task-runner/retrieval-orchestrator.ts +72 -18
  137. package/src/runtime/task-runner/spec-evidence.ts +403 -0
  138. package/src/runtime/task-runner/state-helpers.ts +26 -24
  139. package/src/runtime/task-runner.ts +11 -0
  140. package/src/runtime/team-runner.ts +132 -1673
  141. package/src/runtime/verification/spec-sandbox.ts +255 -0
  142. package/src/runtime/verification/verification-gates.ts +3 -2
  143. package/src/runtime/verification/verification-worktree.ts +2 -1
  144. package/src/runtime/workflow-phase-advance.ts +100 -0
  145. package/src/runtime/workspace-tree.ts +9 -0
  146. package/src/schema/config-schema.ts +66 -26
  147. package/src/schema/sensitive-config-paths.ts +64 -0
  148. package/src/schema/team-tool-schema.ts +13 -3
  149. package/src/state/README.md +4 -10
  150. package/src/state/atomic-write.ts +20 -3
  151. package/src/state/contracts.ts +38 -0
  152. package/src/state/coordination/mailbox.ts +12 -2
  153. package/src/state/event-log/cursor.ts +223 -0
  154. package/src/state/event-log/event-log-rotation.ts +12 -4
  155. package/src/state/event-log/event-log.ts +152 -369
  156. package/src/state/event-log/sequence-cache.ts +373 -0
  157. package/src/state/event-log/worker-atomic-writer.ts +2 -1
  158. package/src/state/stores/active-run-registry.ts +3 -2
  159. package/src/state/stores/manifest-io.ts +237 -0
  160. package/src/state/stores/ownership-map.ts +162 -0
  161. package/src/state/stores/plan-store.ts +241 -0
  162. package/src/state/stores/run-cache.ts +0 -90
  163. package/src/state/stores/spec-store.ts +189 -0
  164. package/src/state/stores/state-store.ts +139 -232
  165. package/src/state/types.ts +199 -0
  166. package/src/ui/dashboard-panes/plan-pane.ts +136 -0
  167. package/src/ui/dashboard-panes/progress-pane.ts +6 -0
  168. package/src/ui/dashboard-panes/transcript-pane.ts +31 -0
  169. package/src/ui/dock-footer.ts +49 -0
  170. package/src/ui/heartbeat-aggregator.ts +9 -1
  171. package/src/ui/inline-panel/agent-pane.ts +375 -0
  172. package/src/ui/inline-panel/agent-transcript.ts +338 -0
  173. package/src/ui/inline-panel/agent-view-overlay.ts +225 -0
  174. package/src/ui/inline-panel/crew-editor.ts +192 -0
  175. package/src/ui/inline-panel/index.ts +290 -0
  176. package/src/ui/inline-panel/panel-rows.ts +37 -0
  177. package/src/ui/inline-panel/panel-selection.ts +157 -0
  178. package/src/ui/inline-panel/panel-store.ts +111 -0
  179. package/src/ui/inline-panel/view-session-store.ts +36 -0
  180. package/src/ui/keybinding-map.ts +54 -13
  181. package/src/ui/pi-ui-compat.ts +9 -0
  182. package/src/ui/powerbar-publisher.ts +52 -1
  183. package/src/ui/run-dashboard.ts +31 -5
  184. package/src/ui/run-snapshot-cache.ts +57 -30
  185. package/src/ui/snapshot-types.ts +6 -1
  186. package/src/ui/widget/index.ts +176 -22
  187. package/src/ui/widget/task-list.ts +198 -0
  188. package/src/ui/widget/widget-formatters.ts +240 -4
  189. package/src/ui/widget/widget-renderer.ts +243 -38
  190. package/src/ui/widget/widget-types.ts +11 -0
  191. package/src/utils/child-process-shield.ts +106 -0
  192. package/src/utils/file-coalescer.ts +0 -4
  193. package/src/utils/fs-errno.ts +66 -0
  194. package/src/utils/fs-watch.ts +1 -1
  195. package/src/utils/internal-error.ts +3 -1
  196. package/src/utils/paths.ts +11 -3
  197. package/src/utils/redaction.ts +7 -0
  198. package/src/utils/safe-abort.ts +45 -0
  199. package/src/utils/task-name-generator.ts +1 -8
  200. package/src/workflows/discover-workflows.ts +20 -2
  201. package/src/workflows/validate-workflow.ts +7 -1
  202. package/src/workflows/workflow-config.ts +17 -0
  203. package/src/workflows/workflow-serializer.ts +3 -0
  204. package/src/worktree/worktree-manager.ts +22 -0
  205. package/workflows/default.workflow.md +36 -26
  206. package/workflows/strict-fast-fix.workflow.md +26 -0
  207. package/src/agents/agent-search.ts +0 -98
  208. package/src/benchmark/benchmark-runner.ts +0 -313
  209. package/src/benchmark/feedback-loop.ts +0 -73
  210. package/src/config/resilient-parser.ts +0 -117
  211. package/src/extension/crew-vibes/cat-frames.ts +0 -18
  212. package/src/extension/result-watcher.ts +0 -139
  213. package/src/observability/exporters/prometheus-exporter.ts +0 -54
  214. package/src/observability/metric-retention.ts +0 -64
  215. package/src/runtime/compaction/compaction-summary.ts +0 -278
  216. package/src/runtime/errors/crew-errors.ts +0 -162
  217. package/src/runtime/live-session/intercom-bridge.ts +0 -187
  218. package/src/runtime/loop-gates.ts +0 -128
  219. package/src/runtime/metric-parser.ts +0 -36
  220. package/src/runtime/output/stream-preview.ts +0 -184
  221. package/src/runtime/output/tool-progress.ts +0 -278
  222. package/src/runtime/phase-tracker.ts +0 -385
  223. package/src/runtime/pipeline-runner.ts +0 -523
  224. package/src/runtime/process/process-lifecycle.ts +0 -491
  225. package/src/runtime/recovery/retry-runner.ts +0 -330
  226. package/src/runtime/run-drift.ts +0 -219
  227. package/src/runtime/task-quality.ts +0 -199
  228. package/src/runtime/task-runner/run-projection.ts +0 -128
  229. package/src/runtime/verification/post-checks.ts +0 -142
  230. package/src/state/coordination/schedule.ts +0 -166
  231. package/src/state/event-log/jsonl-writer.ts +0 -115
  232. package/src/state/hook-instinct-bridge.ts +0 -94
  233. package/src/state/hook-integrations.ts +0 -51
  234. package/src/state/session-state-map.ts +0 -51
  235. package/src/state/stores/blob-store.ts +0 -308
  236. package/src/state/stores/instinct-store.ts +0 -275
  237. package/src/state/stores/observation-store.ts +0 -176
  238. package/src/state/tiered-eval.ts +0 -480
  239. package/src/state/types-eval.ts +0 -58
  240. package/src/tools/safe-bash-extension.ts +0 -54
  241. package/src/tools/safe-bash.ts +0 -505
  242. package/src/ui/agent-management-overlay.ts +0 -160
  243. package/src/ui/crew-footer.ts +0 -102
  244. package/src/ui/crew-select-list.ts +0 -114
  245. package/src/ui/dashboard-panes/capability-pane.ts +0 -77
  246. package/src/ui/transcript-entries.ts +0 -256
  247. package/src/utils/conflict-detect.ts +0 -721
  248. package/src/utils/fingerprint.ts +0 -180
  249. package/src/utils/gh-protocol.ts +0 -556
  250. package/src/utils/project-detector.ts +0 -160
  251. package/src/utils/sse-parser.ts +0 -131
  252. package/src/workflows/cost-estimator.ts +0 -34
  253. package/src/workflows/intermediate-store.ts +0 -166
@@ -1,523 +0,0 @@
1
- import { errors } from "../errors.ts";
2
- import { appendEventAsync, flushEventLogBuffer } from "../state/event-log/event-log.ts";
3
- import { errorMessage } from "../utils/guards.ts";
4
- import type { WorkflowConfig } from "../workflows/workflow-config.ts";
5
- import { mapConcurrent } from "./scheduling/parallel-utils.ts";
6
-
7
- /**
8
- * Pipeline stage configuration.
9
- */
10
- export interface PipelineStage {
11
- name: string;
12
- team: string;
13
- inputs: unknown;
14
- /** Enable fan-out when inputs is an array (default: true) */
15
- fanOut?: boolean;
16
- /** Maximum concurrent executions for fan-out (default: 5) */
17
- maxConcurrency?: number;
18
- /** Stop pipeline if this stage fails (default: true) */
19
- stopOnError?: boolean;
20
- /** Pass previous stage results as inputs (default: true) */
21
- usePreviousResults?: boolean;
22
- }
23
-
24
- /**
25
- * Pipeline workflow configuration.
26
- */
27
- export interface PipelineWorkflow {
28
- name: string;
29
- description: string;
30
- goal: string;
31
- stages: PipelineStage[];
32
- /** Stop pipeline if any stage fails (default: true) */
33
- stopOnError?: boolean;
34
- /** Default max concurrency for fan-out stages */
35
- defaultMaxConcurrency?: number;
36
- /** Context passed to all stages */
37
- context?: Record<string, unknown>;
38
- }
39
-
40
- /**
41
- * Context passed to each stage execution.
42
- */
43
- export interface PipelineContext {
44
- stageIndex: number;
45
- stageName: string;
46
- previousResults: unknown[];
47
- totalStages: number;
48
- runId: string;
49
- }
50
-
51
- /**
52
- * Result of a single stage execution.
53
- */
54
- export interface StageResult {
55
- name: string;
56
- status: "completed" | "failed" | "skipped";
57
- results: unknown[];
58
- error?: string;
59
- duration: number;
60
- fanOutItems?: number;
61
- }
62
-
63
- /**
64
- * Complete pipeline execution result.
65
- */
66
- export interface PipelineResult {
67
- stages: StageResult[];
68
- totalDuration: number;
69
- finalResults: unknown[];
70
- status: "completed" | "failed" | "partial";
71
- }
72
-
73
- /**
74
- * PipelineRunner executes multi-stage workflows with automatic fan-out
75
- * for array inputs.
76
- */
77
- export class PipelineRunner {
78
- private stopOnError: boolean;
79
- private defaultMaxConcurrency: number;
80
-
81
- constructor(options?: {
82
- stopOnError?: boolean;
83
- defaultMaxConcurrency?: number;
84
- }) {
85
- this.stopOnError = options?.stopOnError ?? true;
86
- this.defaultMaxConcurrency = options?.defaultMaxConcurrency ?? 5;
87
- }
88
-
89
- /**
90
- * Execute a pipeline workflow.
91
- * @param workflow - The pipeline workflow definition
92
- * @param context - Additional context for execution
93
- * @param executeStage - Function to execute a single stage
94
- * @param runId - Run identifier for event logging
95
- * @param eventsPath - Path to event log file
96
- */
97
- async run(
98
- workflow: PipelineWorkflow,
99
- context: Record<string, unknown>,
100
- executeStage: (stage: PipelineStage, inputs: unknown, stageContext: PipelineContext) => Promise<unknown>,
101
- runId: string,
102
- eventsPath: string,
103
- ): Promise<PipelineResult> {
104
- const stages: StageResult[] = [];
105
- let previousResults: unknown[] = [];
106
- const startTime = Date.now();
107
-
108
- try {
109
- await appendEventAsync(eventsPath, {
110
- type: "pipeline:started",
111
- runId,
112
- message: `Pipeline '${workflow.name}' started`,
113
- data: { stages: workflow.stages.map((s) => s.name) },
114
- });
115
-
116
- for (let i = 0; i < workflow.stages.length; i++) {
117
- const stage = workflow.stages[i];
118
- const stageStartTime = Date.now();
119
-
120
- // Determine stop behavior for this stage
121
- const effectiveStopOnError = stage.stopOnError ?? workflow.stopOnError ?? this.stopOnError;
122
-
123
- await appendEventAsync(eventsPath, {
124
- type: "pipeline:stage_started",
125
- runId,
126
- message: `Stage '${stage.name}' started`,
127
- data: { stageIndex: i, stageName: stage.name },
128
- });
129
-
130
- try {
131
- // Build stage context
132
- const stageContext: PipelineContext = {
133
- stageIndex: i,
134
- stageName: stage.name,
135
- previousResults,
136
- totalStages: workflow.stages.length,
137
- runId,
138
- };
139
-
140
- // Resolve inputs
141
- const inputs = this.resolveInputs(stage.inputs, previousResults, context);
142
-
143
- // Execute stage (handle fan-out if enabled)
144
- const results = await this.executeStageInternal(stage, inputs, stageContext, executeStage);
145
-
146
- const duration = Date.now() - stageStartTime;
147
- stages.push({
148
- name: stage.name,
149
- status: "completed",
150
- results,
151
- duration,
152
- fanOutItems: Array.isArray(inputs) ? inputs.length : undefined,
153
- });
154
-
155
- previousResults = results;
156
-
157
- await appendEventAsync(eventsPath, {
158
- type: "pipeline:stage_completed",
159
- runId,
160
- message: `Stage '${stage.name}' completed`,
161
- data: {
162
- stageIndex: i,
163
- stageName: stage.name,
164
- duration,
165
- resultCount: results.length,
166
- },
167
- });
168
- } catch (error) {
169
- const duration = Date.now() - stageStartTime;
170
- const errMsg = errorMessage(error);
171
-
172
- if (effectiveStopOnError) {
173
- stages.push({
174
- name: stage.name,
175
- status: "failed",
176
- results: [],
177
- error: errMsg,
178
- duration,
179
- });
180
-
181
- await appendEventAsync(eventsPath, {
182
- type: "pipeline:stage_failed",
183
- runId,
184
- message: `Stage '${stage.name}' failed: ${errMsg}`,
185
- data: {
186
- stageIndex: i,
187
- stageName: stage.name,
188
- duration,
189
- error: errMsg,
190
- },
191
- });
192
-
193
- await appendEventAsync(eventsPath, {
194
- type: "pipeline:failed",
195
- runId,
196
- message: `Pipeline '${workflow.name}' failed at stage '${stage.name}'`,
197
- data: { failedStage: stage.name, error: errMsg },
198
- });
199
-
200
- return {
201
- stages,
202
- totalDuration: Date.now() - startTime,
203
- finalResults: previousResults,
204
- status: "failed",
205
- };
206
- } else {
207
- stages.push({
208
- name: stage.name,
209
- status: "failed",
210
- results: [],
211
- error: errMsg,
212
- duration,
213
- });
214
-
215
- await appendEventAsync(eventsPath, {
216
- type: "pipeline:stage_skipped",
217
- runId,
218
- message: `Stage '${stage.name}' skipped due to error`,
219
- data: {
220
- stageIndex: i,
221
- stageName: stage.name,
222
- duration,
223
- error: errMsg,
224
- },
225
- });
226
- }
227
- }
228
- }
229
-
230
- await appendEventAsync(eventsPath, {
231
- type: "pipeline:completed",
232
- runId,
233
- message: `Pipeline '${workflow.name}' completed`,
234
- data: {
235
- stages: stages.map((s) => ({ name: s.name, status: s.status })),
236
- },
237
- });
238
-
239
- return {
240
- stages,
241
- totalDuration: Date.now() - startTime,
242
- finalResults: previousResults,
243
- status: stages.some((s) => s.status === "failed") ? "partial" : "completed",
244
- };
245
- } finally {
246
- // FIX (P0 follow-up): Drain the event-log buffer before returning so
247
- // --test-force-exit does not detect "Promise resolution is still
248
- // pending". The buffered flush uses a 20ms ref'd timer that fires
249
- // asynchronously; without this explicit drain, the test runner sees
250
- // pending appendEventAsync promises and cancels the test file.
251
- await flushEventLogBuffer();
252
- }
253
- }
254
-
255
- /**
256
- * Execute a single stage, handling fan-out for array inputs.
257
- * Uses depth parameter to prevent stack overflow from deep recursion.
258
- */
259
- private async executeStageInternal(
260
- stage: PipelineStage,
261
- inputs: unknown,
262
- stageContext: PipelineContext,
263
- callback: (stage: PipelineStage, inputs: unknown, stageContext: PipelineContext) => Promise<unknown>,
264
- depth: number = 0,
265
- ): Promise<unknown[]> {
266
- // CRITICAL-6: Prevent stack overflow from deep recursion
267
- if (depth > 50) {
268
- // E1 (Round 15): structured CrewError (E011) with help hint.
269
- throw errors.depthLimitExceeded(depth, "pipeline");
270
- }
271
-
272
- const fanOut = stage.fanOut ?? true;
273
- const maxConcurrency = stage.maxConcurrency ?? this.defaultMaxConcurrency;
274
-
275
- // Fan-out if inputs is an array with multiple items to process.
276
- // We don't fan-out for single-element arrays as they typically represent
277
- // the result of a previous stage that returned a single value.
278
- const shouldFanOut = fanOut && Array.isArray(inputs) && inputs.length > 1;
279
-
280
- // Increment depth for non-fan-out path
281
- const nextDepth = depth + 1;
282
-
283
- if (shouldFanOut) {
284
- const tasks = (inputs as unknown[]).map((item, index) => ({
285
- item,
286
- index,
287
- name: `${stage.name}[${index}]`,
288
- }));
289
-
290
- // Execute with concurrency limit - pass each item to callback
291
- const results = await mapConcurrent(tasks, maxConcurrency, async (task) => {
292
- // Call the user-provided callback with each item
293
- const result = await this.executeStageInternal(
294
- stage,
295
- task.item,
296
- {
297
- ...stageContext,
298
- stageName: task.name,
299
- },
300
- callback,
301
- nextDepth,
302
- );
303
- return result;
304
- });
305
-
306
- return results;
307
- }
308
-
309
- // Single execution - pass inputs directly to callback
310
- const result = await callback(stage, inputs, stageContext);
311
- return [result];
312
- }
313
-
314
- /**
315
- * Resolve inputs from template strings and previous results.
316
- * Supports JMESPath-like resolution:
317
- * - ${previous} -> previousResults
318
- * - ${previous[0]} -> previousResults[0]
319
- * - ${context.key} -> context.key
320
- * - ${args.x} -> context.args.x
321
- *
322
- * C5: Validates template inputs to prevent injection.
323
- */
324
- private resolveInputs(inputs: unknown, previousResults: unknown[], context: Record<string, unknown>): unknown {
325
- // If inputs is an array, resolve each element
326
- if (Array.isArray(inputs)) {
327
- // H4: Type safety - limit array size to prevent memory issues
328
- const maxItems = 10000;
329
- const limitedInputs = inputs.length > maxItems ? inputs.slice(0, maxItems) : inputs;
330
- return limitedInputs.map((input) => this.resolveInputs(input, previousResults, context));
331
- }
332
-
333
- // If inputs is a string, check for template patterns
334
- if (typeof inputs === "string") {
335
- return this.resolveTemplate(inputs, previousResults, context);
336
- }
337
-
338
- // If inputs is an object, resolve each value
339
- if (typeof inputs === "object" && inputs !== null) {
340
- const resolved: Record<string, unknown> = {};
341
- for (const [key, value] of Object.entries(inputs)) {
342
- // C5: Validate key to prevent prototype pollution
343
- if (this.isValidObjectKey(key)) {
344
- resolved[key] = this.resolveInputs(value as string | string[] | Record<string, unknown>, previousResults, context);
345
- }
346
- }
347
- return resolved;
348
- }
349
-
350
- // Primitive value - return as-is
351
- return inputs;
352
- }
353
-
354
- /**
355
- * C5: Validate object key to prevent prototype pollution and injection.
356
- */
357
- private isValidObjectKey(key: string): boolean {
358
- // Reject dangerous keys
359
- const dangerousKeys = [
360
- "__proto__",
361
- "constructor",
362
- "prototype",
363
- "__defineGetter__",
364
- "__defineSetter__",
365
- "__lookupGetter__",
366
- "__lookupSetter__",
367
- ];
368
- if (dangerousKeys.includes(key)) {
369
- return false;
370
- }
371
- // Reject keys with null bytes or control characters
372
- if (/[\x00-\x1F\x7F]/.test(key)) {
373
- return false;
374
- }
375
- // Reject overly long keys
376
- if (key.length > 256) {
377
- return false;
378
- }
379
- return true;
380
- }
381
-
382
- /**
383
- * C5: Validate nested path (e.g., "nested.deep.value") to prevent injection.
384
- * Each part is validated individually.
385
- */
386
- private isValidNestedPath(path: string): boolean {
387
- // Reject empty paths
388
- if (!path || path.length === 0) {
389
- return false;
390
- }
391
- // Reject overly long paths
392
- if (path.length > 512) {
393
- return false;
394
- }
395
- // Reject paths with empty segments
396
- if (path.includes("..")) {
397
- return false;
398
- }
399
- // Validate each path segment
400
- const parts = path.split(".");
401
- for (const part of parts) {
402
- if (!this.isValidObjectKey(part)) {
403
- return false;
404
- }
405
- }
406
- return true;
407
- }
408
-
409
- /**
410
- * Resolve a single template string.
411
- * C5: Validates template inputs to prevent injection.
412
- */
413
- private resolveTemplate(template: string, previousResults: unknown[], context: Record<string, unknown>): unknown {
414
- // C5: Validate template length to prevent DoS
415
- if (template.length > 10000) {
416
- return template;
417
- }
418
-
419
- // Check for ${previous} pattern
420
- const previousMatch = template.match(/^\$\{previous\}$/);
421
- if (previousMatch) {
422
- return previousResults;
423
- }
424
-
425
- // Check for ${previous[N]} pattern with bounds checking
426
- const previousIndexMatch = template.match(/^\$\{previous\[(\d+)\]\}$/);
427
- if (previousIndexMatch) {
428
- const index = parseInt(previousIndexMatch[1], 10);
429
- // H4: Type safety - validate index bounds
430
- if (index >= 0 && index < previousResults.length) {
431
- return previousResults[index];
432
- }
433
- return undefined;
434
- }
435
-
436
- // Check for ${context.key} pattern with sanitized key extraction
437
- const contextMatch = template.match(/^\$\{context\.([a-zA-Z_][a-zA-Z0-9_.]*)\}$/);
438
- if (contextMatch) {
439
- const key = contextMatch[1];
440
- // C5: Validate the full path (each part validated in getNestedValue)
441
- if (this.isValidNestedPath(key)) {
442
- return this.getNestedValue(context, key);
443
- }
444
- return undefined;
445
- }
446
-
447
- // Check for ${args.key} pattern with sanitized key extraction
448
- const argsMatch = template.match(/^\$\{args\.([a-zA-Z_][a-zA-Z0-9_.]*)\}$/);
449
- if (argsMatch) {
450
- const key = argsMatch[1];
451
- // C5: Validate the full path (each part validated in getNestedValue)
452
- if (this.isValidNestedPath(key)) {
453
- const args = (context.args as Record<string, unknown>) ?? {};
454
- return this.getNestedValue(args, key);
455
- }
456
- return undefined;
457
- }
458
-
459
- // No pattern matched - return template as-is
460
- return template;
461
- }
462
-
463
- /**
464
- * Get nested value from object using dot notation.
465
- * H4: Type safety - validates path and prevents prototype pollution.
466
- */
467
- private getNestedValue(obj: Record<string, unknown>, path: string): unknown {
468
- const parts = path.split(".");
469
- let current: unknown = obj;
470
-
471
- for (const part of parts) {
472
- // H4: Validate each path part
473
- if (!this.isValidObjectKey(part)) {
474
- return undefined;
475
- }
476
- if (current === null || current === undefined) {
477
- return undefined;
478
- }
479
- if (typeof current !== "object") {
480
- return undefined;
481
- }
482
- current = (current as Record<string, unknown>)[part];
483
- }
484
-
485
- return current;
486
- }
487
-
488
- /**
489
- * Parse a pipeline workflow from a workflow configuration.
490
- * Converts standard WorkflowConfig to PipelineWorkflow.
491
- */
492
- static fromWorkflowConfig(workflow: WorkflowConfig, goal: string): PipelineWorkflow {
493
- const stages: PipelineStage[] = workflow.steps.map((step) => ({
494
- name: step.id,
495
- team: step.role, // Using role as team identifier
496
- inputs: step.task,
497
- usePreviousResults: step.dependsOn && step.dependsOn.length > 0,
498
- }));
499
-
500
- return {
501
- name: workflow.name,
502
- description: workflow.description,
503
- goal,
504
- stages,
505
- stopOnError: true,
506
- defaultMaxConcurrency: workflow.maxConcurrency ?? 5,
507
- };
508
- }
509
- }
510
-
511
- /**
512
- * Create a pipeline workflow from a goal and stage definitions.
513
- */
514
- export function createPipelineWorkflow(name: string, description: string, goal: string, stages: PipelineStage[]): PipelineWorkflow {
515
- return {
516
- name,
517
- description,
518
- goal,
519
- stages,
520
- stopOnError: true,
521
- defaultMaxConcurrency: 5,
522
- };
523
- }