@bastani/atomic 0.8.17-0 → 0.8.18-0

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 (53) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +5 -0
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/CHANGELOG.md +5 -0
  5. package/dist/builtin/mcp/package.json +1 -1
  6. package/dist/builtin/subagents/CHANGELOG.md +5 -0
  7. package/dist/builtin/subagents/package.json +1 -1
  8. package/dist/builtin/web-access/CHANGELOG.md +5 -0
  9. package/dist/builtin/web-access/package.json +1 -1
  10. package/dist/builtin/workflows/CHANGELOG.md +25 -0
  11. package/dist/builtin/workflows/README.md +62 -3
  12. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +555 -537
  13. package/dist/builtin/workflows/builtin/goal.ts +5 -0
  14. package/dist/builtin/workflows/builtin/open-claude-design.ts +3 -3
  15. package/dist/builtin/workflows/builtin/ralph.ts +737 -713
  16. package/dist/builtin/workflows/builtin/shared-prompts.ts +11 -0
  17. package/dist/builtin/workflows/package.json +1 -1
  18. package/dist/builtin/workflows/src/extension/discovery.ts +61 -22
  19. package/dist/builtin/workflows/src/extension/index.ts +2 -0
  20. package/dist/builtin/workflows/src/extension/runtime.ts +4 -0
  21. package/dist/builtin/workflows/src/extension/workflow-schema.ts +4 -0
  22. package/dist/builtin/workflows/src/runs/foreground/executor.ts +96 -6
  23. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +2 -0
  24. package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +7 -0
  25. package/dist/builtin/workflows/src/runs/shared/worktree.ts +214 -1
  26. package/dist/builtin/workflows/src/sdk-surface.ts +2 -0
  27. package/dist/builtin/workflows/src/shared/types.ts +32 -3
  28. package/dist/builtin/workflows/src/workflows/define-workflow.ts +18 -1
  29. package/dist/core/agent-session-services.d.ts +2 -1
  30. package/dist/core/agent-session-services.d.ts.map +1 -1
  31. package/dist/core/agent-session-services.js +1 -0
  32. package/dist/core/agent-session-services.js.map +1 -1
  33. package/dist/core/agent-session.d.ts +3 -0
  34. package/dist/core/agent-session.d.ts.map +1 -1
  35. package/dist/core/agent-session.js +16 -5
  36. package/dist/core/agent-session.js.map +1 -1
  37. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  38. package/dist/core/atomic-guide-command.js +40 -28
  39. package/dist/core/atomic-guide-command.js.map +1 -1
  40. package/dist/core/sdk.d.ts +9 -1
  41. package/dist/core/sdk.d.ts.map +1 -1
  42. package/dist/core/sdk.js +2 -2
  43. package/dist/core/sdk.js.map +1 -1
  44. package/dist/core/system-prompt.d.ts +2 -0
  45. package/dist/core/system-prompt.d.ts.map +1 -1
  46. package/dist/core/system-prompt.js +22 -13
  47. package/dist/core/system-prompt.js.map +1 -1
  48. package/docs/quickstart.md +13 -5
  49. package/docs/sdk.md +20 -5
  50. package/docs/workflows.md +44 -17
  51. package/examples/sdk/05-tools.ts +22 -1
  52. package/examples/sdk/README.md +7 -3
  53. package/package.json +1 -1
@@ -0,0 +1,11 @@
1
+ export const WORKER_PREFLIGHT_CONTRACT = [
2
+ "Before normal implementation delegation, determine whether this checkout appears initialized for its actual language, framework, and build system.",
3
+ "Do not rely on hard-coded assumptions about JavaScript, TypeScript, Python, Rust, Go, Java, mobile, or any other ecosystem. Infer the project type and setup requirements from repository evidence.",
4
+ "Inspect source layout, setup docs, package/build manifests, lockfiles, toolchain files, generated-artifact conventions, CI workflows, workflow configuration, and package scripts or equivalent task definitions.",
5
+ "Look for evidence that dependencies, generated files, local toolchains, submodules, codegen outputs, or other project-specific initialization artifacts are missing for this checkout.",
6
+ "When repository evidence shows missing initialization, run or delegate the appropriate documented setup command before implementation work.",
7
+ "You are responsible for initializing the checkout when setup commands are documented; missing dependencies, generated files, or local toolchains are setup work, not user handoff work.",
8
+ "Once setup succeeds, continue normal implementation orchestration. Do not treat missing dependencies or generated setup artifacts in a fresh worktree as implementation failures.",
9
+ "If setup requirements cannot be determined confidently, delegate a focused discovery task before implementation instead of guessing.",
10
+ "If setup remains blocked after evidence-based discovery and setup attempts, report the blocker with commands tried and the exact evidence needed to continue.",
11
+ ].join("\n");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.8.17-0",
3
+ "version": "0.8.18-0",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -149,19 +149,17 @@ export interface DiscoveryResult {
149
149
  // ---------------------------------------------------------------------------
150
150
 
151
151
  /**
152
- * Validate a candidate value as a WorkflowDefinition.
152
+ * Validate a candidate value as a WorkflowDefinition by shape only.
153
+ *
154
+ * Discovery intentionally does not invoke workflow run functions: user-authored
155
+ * run bodies may perform filesystem, network, or other side effects before the
156
+ * first ctx.stage()/ctx.task()/ctx.chain()/ctx.parallel() call. Runtime empty
157
+ * graph validation remains the authoritative guard that a workflow creates at
158
+ * least one stage when it is actually invoked.
159
+ *
153
160
  * Returns null when valid, or a human-readable rejection reason string.
154
161
  */
155
- function workflowRunCreatesStage(run: WorkflowDefinition["run"]): boolean {
156
- // Discovery must not execute workflow bodies: run functions are arbitrary
157
- // user code and may perform I/O before the first stage. Use a conservative
158
- // static guard instead so obvious no-stage definitions are rejected before
159
- // they can register and render an empty graph.
160
- const source = Function.prototype.toString.call(run);
161
- return /\.\s*(?:stage|task|chain|parallel)\s*\(/.test(source);
162
- }
163
-
164
- function validateDefinition(value: unknown): string | null {
162
+ function validateDefinitionShape(value: unknown): string | null {
165
163
  if (value === null || typeof value !== "object") {
166
164
  return "export is not an object";
167
165
  }
@@ -179,9 +177,6 @@ function validateDefinition(value: unknown): string | null {
179
177
  if (typeof d["run"] !== "function") {
180
178
  return "run must be a function";
181
179
  }
182
- if (!workflowRunCreatesStage(d["run"] as WorkflowDefinition["run"])) {
183
- return "run must create at least one workflow stage via ctx.stage(), ctx.task(), ctx.chain(), or ctx.parallel(); otherwise the workflow graph is empty (cachedLayout.length === 0)";
184
- }
185
180
  return null;
186
181
  }
187
182
 
@@ -215,14 +210,58 @@ function validateConfig(config: unknown): string | null {
215
210
  }
216
211
 
217
212
  /** Merge a batch of candidates into registry state, first-seen wins. */
218
- function applyBatch(
213
+ async function applyBatch(
214
+ candidates: Array<{ value: unknown; exportKey: string; kind: DiscoveryKind; filePath?: string; configuredName?: string }>,
215
+ registry: WorkflowRegistry,
216
+ sources: DiscoverySource[],
217
+ diagnostics: DiscoveryDiagnostic[],
218
+ ): Promise<WorkflowRegistry> {
219
+ for (const { value, exportKey, kind, filePath, configuredName } of candidates) {
220
+ const reason = validateDefinitionShape(value);
221
+ if (reason !== null) {
222
+ diagnostics.push({
223
+ level: "error",
224
+ code: "INVALID_DEFINITION",
225
+ message: `${kind} export "${exportKey}" rejected: ${reason}`,
226
+ source: filePath ?? exportKey,
227
+ });
228
+ continue;
229
+ }
230
+
231
+ const def = value as WorkflowDefinition;
232
+ const key = def.normalizedName;
233
+
234
+ if (registry.has(key)) {
235
+ diagnostics.push({
236
+ level: "warn",
237
+ code: "DUPLICATE_NAME",
238
+ message: `${kind} export "${exportKey}" skipped: normalizedName "${key}" already registered`,
239
+ source: filePath ?? exportKey,
240
+ });
241
+ continue;
242
+ }
243
+
244
+ registry = registry.register(def);
245
+ sources.push({
246
+ id: key,
247
+ kind,
248
+ name: def.name,
249
+ ...(filePath !== undefined ? { filePath } : {}),
250
+ ...(configuredName !== undefined ? { configuredName } : {}),
251
+ });
252
+ }
253
+ return registry;
254
+ }
255
+
256
+ /** Merge bundled startup candidates with shape-only validation to keep startup seeding synchronous. */
257
+ function applyBatchShapeOnly(
219
258
  candidates: Array<{ value: unknown; exportKey: string; kind: DiscoveryKind; filePath?: string; configuredName?: string }>,
220
259
  registry: WorkflowRegistry,
221
260
  sources: DiscoverySource[],
222
261
  diagnostics: DiscoveryDiagnostic[],
223
262
  ): WorkflowRegistry {
224
263
  for (const { value, exportKey, kind, filePath, configuredName } of candidates) {
225
- const reason = validateDefinition(value);
264
+ const reason = validateDefinitionShape(value);
226
265
  if (reason !== null) {
227
266
  diagnostics.push({
228
267
  level: "error",
@@ -508,14 +547,14 @@ export async function discoverWorkflows(
508
547
  const hasEntries = Array.isArray(pw) ? pw.length > 0 : Object.keys(pw).length > 0;
509
548
  if (hasEntries) {
510
549
  const candidates = await loadFromPaths(pw, "settings-project", cwd, diagnostics);
511
- registry = applyBatch(candidates, registry, sources, diagnostics);
550
+ registry = await applyBatch(candidates, registry, sources, diagnostics);
512
551
  }
513
552
  }
514
553
 
515
554
  // 2. project-local
516
555
  for (const dir of getProjectConfigPaths(cwd, "workflows").reverse()) {
517
556
  const candidates = await loadFromDir(dir, "project-local", diagnostics);
518
- registry = applyBatch(candidates, registry, sources, diagnostics);
557
+ registry = await applyBatch(candidates, registry, sources, diagnostics);
519
558
  }
520
559
 
521
560
  // 3. settings-global
@@ -524,14 +563,14 @@ export async function discoverWorkflows(
524
563
  const hasEntries = Array.isArray(gw) ? gw.length > 0 : Object.keys(gw).length > 0;
525
564
  if (hasEntries) {
526
565
  const candidates = await loadFromPaths(gw, "settings-global", homeDir, diagnostics);
527
- registry = applyBatch(candidates, registry, sources, diagnostics);
566
+ registry = await applyBatch(candidates, registry, sources, diagnostics);
528
567
  }
529
568
  }
530
569
 
531
570
  // 4. user-global — canonical Atomic path plus legacy pi path
532
571
  for (const dir of CONFIG_DIR_NAMES.map((name) => join(homeDir, name, "agent", "workflows")).reverse()) {
533
572
  const candidates = await loadFromDir(dir, "user-global", diagnostics);
534
- registry = applyBatch(candidates, registry, sources, diagnostics);
573
+ registry = await applyBatch(candidates, registry, sources, diagnostics);
535
574
  }
536
575
 
537
576
  // 5. package workflows
@@ -539,7 +578,7 @@ export async function discoverWorkflows(
539
578
  const hasEntries = Array.isArray(packageWorkflowPaths) ? packageWorkflowPaths.length > 0 : Object.keys(packageWorkflowPaths).length > 0;
540
579
  if (hasEntries) {
541
580
  const candidates = await loadFromPaths(packageWorkflowPaths, "package", cwd, diagnostics);
542
- registry = applyBatch(candidates, registry, sources, diagnostics);
581
+ registry = await applyBatch(candidates, registry, sources, diagnostics);
543
582
  }
544
583
  }
545
584
 
@@ -602,7 +641,7 @@ function discoverBundledManifest(): DiscoveryResult {
602
641
  kind: "bundled" as DiscoveryKind,
603
642
  }));
604
643
 
605
- registry = applyBatch(candidates, registry, sources, diagnostics);
644
+ registry = applyBatchShapeOnly(candidates, registry, sources, diagnostics);
606
645
 
607
646
  return { registry, sources, errors: diagnostics };
608
647
  }
@@ -456,6 +456,8 @@ export interface WorkflowToolArgs extends StageOptions {
456
456
  maxOutput?: WorkflowMaxOutput;
457
457
  artifacts?: boolean;
458
458
  worktree?: boolean;
459
+ gitWorktreeDir?: string;
460
+ baseBranch?: string;
459
461
  }
460
462
 
461
463
  // ---------------------------------------------------------------------------
@@ -190,6 +190,8 @@ export function createExtensionRuntime(opts: ExtensionRuntimeOpts = {}): Extensi
190
190
  maxOutput,
191
191
  artifacts,
192
192
  worktree,
193
+ gitWorktreeDir,
194
+ baseBranch,
193
195
  ...stageOptions
194
196
  } = args;
195
197
 
@@ -206,6 +208,8 @@ export function createExtensionRuntime(opts: ExtensionRuntimeOpts = {}): Extensi
206
208
  ...(maxOutput !== undefined ? { maxOutput } : {}),
207
209
  ...(typeof artifacts === "boolean" ? { artifacts } : {}),
208
210
  ...(typeof worktree === "boolean" ? { worktree } : {}),
211
+ ...(typeof gitWorktreeDir === "string" ? { gitWorktreeDir } : {}),
212
+ ...(typeof baseBranch === "string" ? { baseBranch } : {}),
209
213
  };
210
214
  }
211
215
 
@@ -66,6 +66,8 @@ const WorkflowTaskOptionProperties = {
66
66
  outputMode: Type.Optional(Type.Union([Type.Literal("inline"), Type.Literal("file-only")])),
67
67
  reads: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Literal(false)])),
68
68
  worktree: Type.Optional(Type.Boolean()),
69
+ gitWorktreeDir: Type.Optional(Type.String()),
70
+ baseBranch: Type.Optional(Type.String()),
69
71
  maxOutput: Type.Optional(MaxOutputSchema),
70
72
  artifacts: Type.Optional(Type.Boolean()),
71
73
  };
@@ -83,6 +85,8 @@ const ParallelChainStepSchema = Type.Object({
83
85
  concurrency: Type.Optional(Type.Number()),
84
86
  failFast: Type.Optional(Type.Boolean()),
85
87
  worktree: Type.Optional(Type.Boolean()),
88
+ gitWorktreeDir: Type.Optional(Type.String()),
89
+ baseBranch: Type.Optional(Type.String()),
86
90
  });
87
91
 
88
92
  export const WorkflowParametersSchema = Type.Object({
@@ -56,6 +56,7 @@ import {
56
56
  createWorktrees,
57
57
  diffWorktrees,
58
58
  findWorktreeTaskCwdConflict,
59
+ setupGitWorktree,
59
60
  formatWorktreeDiffSummary,
60
61
  formatWorktreeTaskCwdConflict,
61
62
  type WorktreeSetup,
@@ -82,6 +83,8 @@ export interface RunContinuationOpts {
82
83
 
83
84
  export interface RunOpts {
84
85
  adapters?: StageAdapters;
86
+ /** Invocation working directory exposed to workflow definitions as ctx.cwd. */
87
+ cwd?: string;
85
88
  /** HIL adapter injected by the pi runtime or test harness. */
86
89
  ui?: WorkflowUIAdapter;
87
90
  /** Internal detached-run mode: surface ctx.ui.* as node-local workflow prompt stages. */
@@ -186,6 +189,23 @@ function resolveInputConcurrency(
186
189
  return Math.floor(value);
187
190
  }
188
191
 
192
+ function resolveInputRuntimeDefaults(
193
+ def: Pick<WorkflowDefinition, "inputBindings">,
194
+ resolvedInputs: ResolvedInputs,
195
+ ): Partial<StageOptions> {
196
+ const defaults: Partial<StageOptions> = {};
197
+ const worktree = def.inputBindings?.worktree;
198
+ if (worktree !== undefined) {
199
+ const gitWorktreeDir = resolvedInputs[worktree.gitWorktreeDir];
200
+ if (typeof gitWorktreeDir === "string" && gitWorktreeDir.trim().length > 0) {
201
+ defaults.gitWorktreeDir = gitWorktreeDir;
202
+ const baseBranch = worktree.baseBranch === undefined ? undefined : resolvedInputs[worktree.baseBranch];
203
+ if (typeof baseBranch === "string") defaults.baseBranch = baseBranch;
204
+ }
205
+ }
206
+ return defaults;
207
+ }
208
+
189
209
  // ---------------------------------------------------------------------------
190
210
  // HIL unavailable fallback — rejects with precise per-primitive error
191
211
  // ---------------------------------------------------------------------------
@@ -397,6 +417,8 @@ function taskStageOptions(options: WorkflowTaskExecutionOptions): StageOptions {
397
417
  outputMode: _outputMode,
398
418
  reads: _reads,
399
419
  worktree: _worktree,
420
+ gitWorktreeDir: _gitWorktreeDir,
421
+ baseBranch: _baseBranch,
400
422
  maxOutput: _maxOutput,
401
423
  artifacts: _artifacts,
402
424
  ...stageOptions
@@ -550,6 +572,8 @@ function directTaskWithDefaults(
550
572
  output,
551
573
  outputMode,
552
574
  worktree,
575
+ gitWorktreeDir,
576
+ baseBranch,
553
577
  maxOutput,
554
578
  artifacts,
555
579
  ...stageDefaults
@@ -567,6 +591,8 @@ function directTaskWithDefaults(
567
591
  ...(item.output === undefined && output !== undefined ? { output } : {}),
568
592
  ...(item.outputMode === undefined && outputMode !== undefined ? { outputMode } : {}),
569
593
  ...(item.worktree === undefined && worktree !== undefined ? { worktree } : {}),
594
+ ...(item.gitWorktreeDir === undefined && gitWorktreeDir !== undefined ? { gitWorktreeDir } : {}),
595
+ ...(item.baseBranch === undefined && baseBranch !== undefined ? { baseBranch } : {}),
570
596
  ...(item.maxOutput === undefined && maxOutput !== undefined ? { maxOutput } : {}),
571
597
  ...(item.artifacts === undefined && artifacts !== undefined ? { artifacts } : {}),
572
598
  };
@@ -714,6 +740,42 @@ function normalizeDirectTaskCwd(cwd: string | undefined): string | undefined {
714
740
  return isAbsolute(cwd) ? cwd : resolve(process.cwd(), cwd);
715
741
  }
716
742
 
743
+ function resolveWorktreeCwdOverride(cwd: string | undefined, worktreeCwd: string): string | undefined {
744
+ if (cwd === undefined || cwd.length === 0) return undefined;
745
+ return isAbsolute(cwd) ? cwd : resolve(worktreeCwd, cwd);
746
+ }
747
+
748
+ function stageOptionsWithInputDefaults<T extends StageOptions>(options: T | undefined, inputDefaults: Partial<StageOptions>): T | undefined {
749
+ const defaults = withoutUndefinedProperties(inputDefaults);
750
+ if (Object.keys(defaults).length === 0) return options;
751
+ return { ...defaults, ...withoutUndefinedProperties(options ?? {}) } as T;
752
+ }
753
+
754
+ function stageOptionsWithGitWorktree<T extends StageOptions>(options: T | undefined, workflowInvocationCwd: string): T | undefined {
755
+ if (options === undefined) return undefined;
756
+ if (typeof options.gitWorktreeDir !== "string" || options.gitWorktreeDir.trim().length === 0) {
757
+ return options;
758
+ }
759
+ const setup = setupGitWorktree({
760
+ gitWorktreeDir: options.gitWorktreeDir,
761
+ baseBranch: options.baseBranch,
762
+ cwd: workflowInvocationCwd,
763
+ });
764
+ const explicitCwd = resolveWorktreeCwdOverride(options.cwd, setup.cwd);
765
+ return { ...options, gitWorktreeDir: undefined, baseBranch: undefined, cwd: explicitCwd ?? setup.cwd };
766
+ }
767
+
768
+ function workflowCwdWithInputWorktree(inputDefaults: Partial<StageOptions>, workflowInvocationCwd: string): string {
769
+ if (typeof inputDefaults.gitWorktreeDir !== "string" || inputDefaults.gitWorktreeDir.trim().length === 0) {
770
+ return workflowInvocationCwd;
771
+ }
772
+ return setupGitWorktree({
773
+ gitWorktreeDir: inputDefaults.gitWorktreeDir,
774
+ baseBranch: inputDefaults.baseBranch,
775
+ cwd: workflowInvocationCwd,
776
+ }).cwd;
777
+ }
778
+
717
779
  function directWorktreeDiffsDir(options: WorkflowDirectOptions, setup: WorktreeSetup, runId: string, scope: string): string {
718
780
  const baseDir = options.chainDir ?? join(setup.cwd, CONFIG_DIR_NAME, "workflows");
719
781
  return join(baseDir, "worktree-diffs", runId, scope);
@@ -732,6 +794,10 @@ function prepareDirectWorktrees(
732
794
  };
733
795
  }
734
796
 
797
+ if (typeof options.gitWorktreeDir === "string" || tasks.some((task) => typeof task.gitWorktreeDir === "string")) {
798
+ throw new Error("pi-workflows: worktree and gitWorktreeDir are mutually exclusive; use gitWorktreeDir for a reusable worktree or worktree:true for temporary isolated worktrees.");
799
+ }
800
+
735
801
  const sharedCwd = resolveSharedDirectWorktreeCwd(tasks);
736
802
  const conflict = findWorktreeTaskCwdConflict(
737
803
  tasks.map((task) => ({ agent: task.name, cwd: normalizeDirectTaskCwd(task.cwd) })),
@@ -884,6 +950,13 @@ function workflowDetailsFromRun(
884
950
  };
885
951
  }
886
952
 
953
+ const EMPTY_WORKFLOW_GRAPH_ERROR_MESSAGE = "Workflow run completed without creating any workflow stages. Create at least one stage with ctx.stage(), ctx.task(), ctx.chain(), or ctx.parallel().";
954
+
955
+ function assertWorkflowCreatedStage(runSnapshot: RunSnapshot): void {
956
+ if (runSnapshot.stages.length > 0) return;
957
+ throw new Error(EMPTY_WORKFLOW_GRAPH_ERROR_MESSAGE);
958
+ }
959
+
887
960
  function defineDirectWorkflow(
888
961
  name: string,
889
962
  runFn: WorkflowDefinition["run"],
@@ -1010,8 +1083,13 @@ async function runDirectChainStep(
1010
1083
  runId: string,
1011
1084
  ): Promise<{ results: WorkflowTaskResult[]; artifacts: WorkflowArtifact[] }> {
1012
1085
  if ("parallel" in step) {
1013
- const expanded = expandedParallelTasks(step.parallel.map((item) => directTaskWithDefaults(item, options)));
1014
- const stepOptions = { ...options, worktree: options.worktree === true || step.worktree === true };
1086
+ const stepOptions = {
1087
+ ...options,
1088
+ worktree: options.worktree === true || step.worktree === true,
1089
+ ...(step.gitWorktreeDir !== undefined ? { gitWorktreeDir: step.gitWorktreeDir } : {}),
1090
+ ...(step.baseBranch !== undefined ? { baseBranch: step.baseBranch } : {}),
1091
+ };
1092
+ const expanded = expandedParallelTasks(step.parallel.map((item) => directTaskWithDefaults(item, stepOptions)));
1015
1093
  const prepared = prepareDirectWorktrees(expanded, stepOptions, `${runId}-s${index}`, `step-${index}`);
1016
1094
  try {
1017
1095
  const steps = prepared.tasks.map((item) =>
@@ -1485,6 +1563,13 @@ export async function run<TInputs extends Record<string, unknown>>(
1485
1563
  // 4. Create GraphFrontierTracker and per-run ConcurrencyLimiter
1486
1564
  const tracker = new GraphFrontierTracker();
1487
1565
  const inputConcurrency = resolveInputConcurrency(def.inputs, resolvedInputs);
1566
+ const inputRuntimeDefaults = resolveInputRuntimeDefaults(def, resolvedInputs);
1567
+ const workflowInvocationCwd = opts.cwd ?? process.cwd();
1568
+ let workflowCwd: string | undefined;
1569
+ const resolveWorkflowCwd = (): string => {
1570
+ workflowCwd ??= workflowCwdWithInputWorktree(inputRuntimeDefaults, workflowInvocationCwd);
1571
+ return workflowCwd;
1572
+ };
1488
1573
  const limiter = createRunLimiter(inputConcurrency ?? opts.config?.defaultConcurrency);
1489
1574
  interface ReleaseBarrier {
1490
1575
  readonly promise: Promise<void>;
@@ -1819,11 +1904,13 @@ export async function run<TInputs extends Record<string, unknown>>(
1819
1904
  // 5. Build WorkflowRunContext
1820
1905
  const ctx: WorkflowRunContext<TInputs> = {
1821
1906
  inputs: resolvedInputs as TInputs,
1907
+ get cwd() { return resolveWorkflowCwd(); },
1822
1908
  // Prompt nodes and caller-provided UI adapters are mutually exclusive;
1823
1909
  // executor-owned prompt nodes intentionally take precedence when enabled.
1824
1910
  ui: opts.usePromptNodesForUi === true ? buildPromptNodeUiAdapter() : opts.ui ?? makeUnavailableUIContext(),
1825
1911
 
1826
1912
  stage(name: string, options?: StageOptions, stageFailFastScope?: ParallelFailFastScope) {
1913
+ options = stageOptionsWithGitWorktree(stageOptionsWithInputDefaults(options, inputRuntimeDefaults), workflowInvocationCwd);
1827
1914
  // a. Generate stageId
1828
1915
  const stageId = crypto.randomUUID();
1829
1916
 
@@ -2369,16 +2456,17 @@ export async function run<TInputs extends Record<string, unknown>>(
2369
2456
 
2370
2457
  async task(name: string, options: WorkflowTaskOptions, stageFailFastScope?: ParallelFailFastScope): Promise<WorkflowTaskResult> {
2371
2458
  const runTaskOnce = async (taskOptions: WorkflowTaskOptions): Promise<WorkflowTaskResult> => {
2459
+ const resolvedTaskOptions = stageOptionsWithGitWorktree(stageOptionsWithInputDefaults(taskOptions, inputRuntimeDefaults), workflowInvocationCwd) ?? taskOptions;
2372
2460
  const stage = (ctx.stage as typeof ctx.stage & ((stageName: string, stageOptions?: StageOptions, scope?: ParallelFailFastScope) => StageContext))(
2373
2461
  name,
2374
- taskStageOptions(taskOptions),
2462
+ taskStageOptions(resolvedTaskOptions),
2375
2463
  stageFailFastScope,
2376
2464
  );
2377
2465
  const rawText = await stage.prompt(
2378
- applyTaskContext(`${taskReadInstruction(taskOptions)}${taskPrompt(taskOptions)}`, taskPrevious(taskOptions)),
2379
- taskPromptOptions(taskOptions),
2466
+ applyTaskContext(`${taskReadInstruction(resolvedTaskOptions)}${taskPrompt(resolvedTaskOptions)}`, taskPrevious(resolvedTaskOptions)),
2467
+ taskPromptOptions(resolvedTaskOptions),
2380
2468
  );
2381
- const text = truncateTaskOutput(rawText, taskOptions.maxOutput);
2469
+ const text = truncateTaskOutput(rawText, resolvedTaskOptions.maxOutput);
2382
2470
  const sessionId = (() => {
2383
2471
  try {
2384
2472
  return stage.sessionId;
@@ -2475,6 +2563,8 @@ export async function run<TInputs extends Record<string, unknown>>(
2475
2563
  return finalizeKilled(runId, runSnapshot, activeStore, opts.persistence, opts.onRunEnd);
2476
2564
  }
2477
2565
 
2566
+ assertWorkflowCreatedStage(runSnapshot);
2567
+
2478
2568
  const recorded = activeStore.recordRunEnd(runId, "completed", result);
2479
2569
  opts.onRunEnd?.(runId, "completed", result);
2480
2570
 
@@ -144,6 +144,8 @@ function stripWorkflowOnlyOptions(options: StageOptions | undefined): CreateAgen
144
144
  context,
145
145
  forkFromSessionFile,
146
146
  sessionDir,
147
+ gitWorktreeDir: _gitWorktreeDir,
148
+ baseBranch: _baseBranch,
147
149
  ...sessionOptions
148
150
  } = options;
149
151
  if (sessionOptions.sessionManager === undefined) {
@@ -45,6 +45,8 @@ export interface WorkflowDefinition extends StageOptions {
45
45
  output?: string | false;
46
46
  outputMode?: WorkflowOutputMode;
47
47
  worktree?: boolean;
48
+ gitWorktreeDir?: string;
49
+ baseBranch?: string;
48
50
  maxOutput?: WorkflowMaxOutput;
49
51
  artifacts?: boolean;
50
52
  }
@@ -91,6 +93,7 @@ function runOptionsWithAdapters(
91
93
 
92
94
  return {
93
95
  ...options.runOptions,
96
+ cwd: options.cwd ?? options.runOptions?.cwd,
94
97
  ...(config !== undefined ? { config } : {}),
95
98
  adapters: buildRuntimeAdapters(options.pi ?? {}, adapterOptions),
96
99
  store: createStore(),
@@ -164,6 +167,8 @@ function directOptions(definition: WorkflowDefinition): WorkflowDirectOptions {
164
167
  output,
165
168
  outputMode,
166
169
  worktree,
170
+ gitWorktreeDir,
171
+ baseBranch,
167
172
  maxOutput,
168
173
  artifacts,
169
174
  ...stageOptions
@@ -180,6 +185,8 @@ function directOptions(definition: WorkflowDefinition): WorkflowDirectOptions {
180
185
  ...(output !== undefined ? { output } : {}),
181
186
  ...(outputMode !== undefined ? { outputMode } : {}),
182
187
  ...(typeof worktree === "boolean" ? { worktree } : {}),
188
+ ...(typeof gitWorktreeDir === "string" ? { gitWorktreeDir } : {}),
189
+ ...(typeof baseBranch === "string" ? { baseBranch } : {}),
183
190
  ...(maxOutput !== undefined ? { maxOutput } : {}),
184
191
  ...(typeof artifacts === "boolean" ? { artifacts } : {}),
185
192
  };