baa-conductor

git clone 

baa-conductor / packages / step-templates / src
im_wower  ·  2026-03-21

index.ts

  1export const TEMPLATE_TASK_TYPES = [
  2  "feature_impl",
  3  "bugfix",
  4  "review_only",
  5  "ops_change",
  6  "infra_bootstrap"
  7] as const;
  8export type TaskTemplateType = (typeof TEMPLATE_TASK_TYPES)[number];
  9
 10export const TEMPLATE_STEP_KINDS = ["planner", "codex", "shell", "git", "review", "finalize"] as const;
 11export type TemplateStepKind = (typeof TEMPLATE_STEP_KINDS)[number];
 12
 13export const TEMPLATE_PLANNING_STRATEGIES = [
 14  "template_first",
 15  "planner_assisted",
 16  "manual"
 17] as const;
 18export type TemplatePlanningStrategy = (typeof TEMPLATE_PLANNING_STRATEGIES)[number];
 19
 20export const TEMPLATE_STEP_NAMES = [
 21  "prepare_branch",
 22  "inspect_context",
 23  "implement",
 24  "run_tests",
 25  "review_fix",
 26  "commit_push",
 27  "finalize",
 28  "reproduce",
 29  "implement_fix",
 30  "run_targeted_tests",
 31  "prepare_context",
 32  "analyze_diff",
 33  "write_review",
 34  "inspect_ops_context",
 35  "edit_ops_files",
 36  "validate_config",
 37  "inspect_current_ops_state",
 38  "edit_dns_or_nginx_docs",
 39  "validate_nginx_config"
 40] as const;
 41export type TemplateStepName = (typeof TEMPLATE_STEP_NAMES)[number];
 42
 43export type TemplateValue =
 44  | boolean
 45  | number
 46  | string
 47  | null
 48  | TemplateValue[]
 49  | {
 50      [key: string]: TemplateValue;
 51    };
 52
 53export type TemplateInput = Record<string, TemplateValue>;
 54
 55export interface TemplateStep {
 56  stepName: TemplateStepName;
 57  stepKind: TemplateStepKind;
 58  timeoutSec: number;
 59  retryLimit: number;
 60  input: TemplateInput;
 61}
 62
 63export interface TaskTemplateDefinition {
 64  taskType: TaskTemplateType;
 65  summary: string;
 66  defaultReasoning: string;
 67  recommendedStrategies: readonly [
 68    TemplatePlanningStrategy,
 69    ...TemplatePlanningStrategy[]
 70  ];
 71  steps: readonly TemplateStep[];
 72}
 73
 74export interface BuildTemplatePlanOptions {
 75  strategy?: TemplatePlanningStrategy;
 76  reasoning?: string;
 77  riskFlags?: string[];
 78}
 79
 80export interface TemplatePlan {
 81  taskType: TaskTemplateType;
 82  strategy: TemplatePlanningStrategy;
 83  reasoning: string;
 84  steps: TemplateStep[];
 85  riskFlags: string[];
 86}
 87
 88const STEP_TEMPLATES = {
 89  feature_impl: {
 90    taskType: "feature_impl",
 91    summary: "Default feature-delivery flow with explicit context gathering, implementation, validation, review, and handoff.",
 92    defaultReasoning:
 93      "Use the feature implementation template so discovery, coding, validation, and review remain explicit recovery boundaries.",
 94    recommendedStrategies: ["template_first", "planner_assisted"],
 95    steps: [
 96      createStep("prepare_branch", "git", 120, 0, {
 97        action: "checkout_task_branch",
 98        branch_name: "{task.branch_name}",
 99        base_ref: "{task.base_ref}"
100      }),
101      createStep("inspect_context", "codex", 600, 1, {
102        goal: "Read the repository and summarize the files, modules, and tests relevant to the requested feature."
103      }),
104      createStep("implement", "codex", 1800, 1, {
105        goal: "Implement the requested feature within the approved write scope and update nearby tests or fixtures when needed."
106      }),
107      createStep("run_tests", "shell", 1200, 1, {
108        command: "pnpm test",
109        allow_override: true
110      }),
111      createStep("review_fix", "review", 900, 1, {
112        goal: "Review the diff for regressions and apply any follow-up fixes before final handoff."
113      }),
114      createStep("commit_push", "git", 300, 0, {
115        action: "commit_and_push_task_branch",
116        branch_name: "{task.branch_name}"
117      }),
118      createStep("finalize", "finalize", 120, 0, {
119        emit_task_summary: true
120      })
121    ]
122  },
123  bugfix: {
124    taskType: "bugfix",
125    summary: "Bugfix flow with reproduction before the fix and targeted validation after the fix.",
126    defaultReasoning:
127      "Use the standard bugfix template first so reproduction, implementation, and targeted validation stay deterministic.",
128    recommendedStrategies: ["template_first", "planner_assisted"],
129    steps: [
130      createStep("prepare_branch", "git", 120, 0, {
131        action: "checkout_task_branch",
132        branch_name: "{task.branch_name}",
133        base_ref: "{task.base_ref}"
134      }),
135      createStep("reproduce", "shell", 600, 1, {
136        command: "{task.repro_command}",
137        allow_override: true
138      }),
139      createStep("implement_fix", "codex", 1200, 1, {
140        goal: "Implement the requested bug fix without changing unrelated behavior."
141      }),
142      createStep("run_targeted_tests", "shell", 900, 1, {
143        command: "{task.targeted_test_command}",
144        allow_override: true
145      }),
146      createStep("commit_push", "git", 300, 0, {
147        action: "commit_and_push_task_branch",
148        branch_name: "{task.branch_name}"
149      }),
150      createStep("finalize", "finalize", 120, 0, {
151        emit_task_summary: true
152      })
153    ]
154  },
155  review_only: {
156    taskType: "review_only",
157    summary: "Diff review flow for findings-only work with no implementation step.",
158    defaultReasoning:
159      "Use the review-only template because the task needs review output, not repository modifications.",
160    recommendedStrategies: ["template_first"],
161    steps: [
162      createStep("prepare_context", "git", 120, 0, {
163        action: "prepare_review_context",
164        base_ref: "{task.base_ref}"
165      }),
166      createStep("analyze_diff", "review", 900, 1, {
167        goal: "Inspect the diff and enumerate findings, risks, and missing tests."
168      }),
169      createStep("write_review", "review", 600, 0, {
170        goal: "Write the final structured review with file references and severity."
171      }),
172      createStep("finalize", "finalize", 120, 0, {
173        emit_task_summary: true
174      })
175    ]
176  },
177  ops_change: {
178    taskType: "ops_change",
179    summary: "Ops change flow with manual-first strategy and explicit config validation.",
180    defaultReasoning:
181      "Prefer a manual-first ops template because environment-specific configuration changes often need operator confirmation.",
182    recommendedStrategies: ["manual", "template_first"],
183    steps: [
184      createStep("prepare_branch", "git", 120, 0, {
185        action: "checkout_task_branch",
186        branch_name: "{task.branch_name}",
187        base_ref: "{task.base_ref}"
188      }),
189      createStep("inspect_ops_context", "codex", 600, 1, {
190        goal: "Read the current ops configuration and summarize the change surface before editing."
191      }),
192      createStep("edit_ops_files", "codex", 1200, 1, {
193        goal: "Apply the requested ops change to the approved config or docs files."
194      }),
195      createStep("validate_config", "shell", 900, 1, {
196        command: "{task.validation_command}",
197        allow_override: true
198      }),
199      createStep("commit_push", "git", 300, 0, {
200        action: "commit_and_push_task_branch",
201        branch_name: "{task.branch_name}"
202      }),
203      createStep("finalize", "finalize", 120, 0, {
204        emit_task_summary: true
205      })
206    ]
207  },
208  infra_bootstrap: {
209    taskType: "infra_bootstrap",
210    summary: "Infrastructure bootstrap flow for DNS, nginx, and related docs or host setup tasks.",
211    defaultReasoning:
212      "Use the bootstrap template so current state inspection, config edits, and nginx validation stay separated into recoverable steps.",
213    recommendedStrategies: ["template_first", "planner_assisted"],
214    steps: [
215      createStep("prepare_branch", "git", 120, 0, {
216        action: "checkout_task_branch",
217        branch_name: "{task.branch_name}",
218        base_ref: "{task.base_ref}"
219      }),
220      createStep("inspect_current_ops_state", "codex", 600, 1, {
221        goal: "Inspect the current DNS, nginx, or host bootstrap state and summarize what must change."
222      }),
223      createStep("edit_dns_or_nginx_docs", "codex", 1200, 1, {
224        goal: "Apply the bootstrap-related DNS, nginx, or runbook edits required by the task."
225      }),
226      createStep("validate_nginx_config", "shell", 900, 1, {
227        command: "nginx -t",
228        allow_override: true
229      }),
230      createStep("commit_push", "git", 300, 0, {
231        action: "commit_and_push_task_branch",
232        branch_name: "{task.branch_name}"
233      }),
234      createStep("finalize", "finalize", 120, 0, {
235        emit_task_summary: true
236      })
237    ]
238  }
239} as const satisfies Record<TaskTemplateType, TaskTemplateDefinition>;
240
241export const TASK_TEMPLATE_ORDER = [...TEMPLATE_TASK_TYPES];
242export const TASK_TEMPLATES = STEP_TEMPLATES;
243
244export function isTaskTemplateType(value: string): value is TaskTemplateType {
245  return (TEMPLATE_TASK_TYPES as readonly string[]).includes(value);
246}
247
248export function getTaskTemplate(taskType: string): TaskTemplateDefinition | undefined {
249  if (!isTaskTemplateType(taskType)) {
250    return undefined;
251  }
252
253  return STEP_TEMPLATES[taskType];
254}
255
256export function listTaskTemplates(): TaskTemplateDefinition[] {
257  return TASK_TEMPLATE_ORDER.map((taskType) => STEP_TEMPLATES[taskType]);
258}
259
260export function buildTemplatePlan(
261  taskType: TaskTemplateType,
262  options: BuildTemplatePlanOptions = {}
263): TemplatePlan {
264  const template = STEP_TEMPLATES[taskType];
265
266  return {
267    taskType,
268    strategy: options.strategy ?? template.recommendedStrategies[0],
269    reasoning: options.reasoning ?? template.defaultReasoning,
270    steps: template.steps.map((step) => cloneTemplateStep(step)),
271    riskFlags: options.riskFlags ? [...options.riskFlags] : []
272  };
273}
274
275function createStep(
276  stepName: TemplateStepName,
277  stepKind: TemplateStepKind,
278  timeoutSec: number,
279  retryLimit: number,
280  input: TemplateInput
281): TemplateStep {
282  return {
283    stepName,
284    stepKind,
285    timeoutSec,
286    retryLimit,
287    input
288  };
289}
290
291function cloneTemplateStep(step: TemplateStep): TemplateStep {
292  return {
293    stepName: step.stepName,
294    stepKind: step.stepKind,
295    timeoutSec: step.timeoutSec,
296    retryLimit: step.retryLimit,
297    input: cloneTemplateInput(step.input)
298  };
299}
300
301function cloneTemplateInput(input: TemplateInput): TemplateInput {
302  return cloneTemplateValue(input) as TemplateInput;
303}
304
305function cloneTemplateValue(value: TemplateValue): TemplateValue {
306  if (Array.isArray(value)) {
307    return value.map((entry) => cloneTemplateValue(entry));
308  }
309
310  if (isPlainObject(value)) {
311    const next: TemplateInput = {};
312
313    for (const [key, entry] of Object.entries(value)) {
314      next[key] = cloneTemplateValue(entry);
315    }
316
317    return next;
318  }
319
320  return value;
321}
322
323function isPlainObject(value: TemplateValue): value is TemplateInput {
324  return typeof value === "object" && value !== null && !Array.isArray(value);
325}