baa-conductor


baa-conductor / apps / conductor-daemon / src / instructions
codex@macbookpro  ·  2026-03-27

parse.ts

  1import type {
  2  BaaExtractedBlock,
  3  BaaInstructionParams,
  4  BaaInstructionParamsKind,
  5  BaaParsedInstruction
  6} from "./types.js";
  7import { isBaaJsonValue } from "./types.js";
  8
  9const INSTRUCTION_HEADER_PATTERN =
 10  /^@(?<target>[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)::(?<tool>[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*)(?:::(?<inline>.*))?$/u;
 11
 12export class BaaInstructionParseError extends Error {
 13  readonly blockIndex: number;
 14  readonly stage = "parse";
 15
 16  constructor(blockIndex: number, message: string) {
 17    super(message);
 18    this.blockIndex = blockIndex;
 19  }
 20}
 21
 22function normalizeInlineParams(
 23  blockIndex: number,
 24  rawInlineParams: string
 25): { params: BaaInstructionParams; paramsKind: BaaInstructionParamsKind } {
 26  const trimmedInlineParams = rawInlineParams.trim();
 27
 28  if (trimmedInlineParams.startsWith("{") || trimmedInlineParams.startsWith("[")) {
 29    try {
 30      const parsed = JSON.parse(trimmedInlineParams) as unknown;
 31
 32      if (!isBaaJsonValue(parsed)) {
 33        throw new Error("Inline JSON params must resolve to a JSON value.");
 34      }
 35
 36      return {
 37        params: parsed,
 38        paramsKind: "inline_json"
 39      };
 40    } catch (error) {
 41      const message = error instanceof Error ? error.message : String(error);
 42      throw new BaaInstructionParseError(
 43        blockIndex,
 44        `Failed to parse inline JSON params: ${message}`
 45      );
 46    }
 47  }
 48
 49  return {
 50    params: rawInlineParams,
 51    paramsKind: "inline_string"
 52  };
 53}
 54
 55export function parseBaaInstructionBlock(block: BaaExtractedBlock): BaaParsedInstruction {
 56  const instructionText = block.content.replace(/\r\n?/gu, "\n");
 57  const lines = instructionText.split("\n");
 58  const firstLine = lines[0]?.trim() ?? "";
 59
 60  if (firstLine === "") {
 61    throw new BaaInstructionParseError(block.blockIndex, "BAA instruction block is empty.");
 62  }
 63
 64  const headerMatch = firstLine.match(INSTRUCTION_HEADER_PATTERN);
 65
 66  if (!headerMatch?.groups) {
 67    throw new BaaInstructionParseError(
 68      block.blockIndex,
 69      `Invalid BAA instruction header: ${firstLine}`
 70    );
 71  }
 72
 73  const target = headerMatch.groups.target;
 74  const tool = headerMatch.groups.tool;
 75  const rawInlineParams = headerMatch.groups.inline;
 76  const rawBody = lines.length > 1 ? lines.slice(1).join("\n") : null;
 77  const hasBody = rawBody != null && rawBody.trim() !== "";
 78
 79  if (!target || !tool) {
 80    throw new BaaInstructionParseError(block.blockIndex, "BAA instruction target and tool are required.");
 81  }
 82
 83  if (rawInlineParams != null && hasBody) {
 84    throw new BaaInstructionParseError(
 85      block.blockIndex,
 86      "BAA instruction cannot mix inline params with a multiline body."
 87    );
 88  }
 89
 90  if (rawInlineParams != null) {
 91    const normalizedInlineParams = normalizeInlineParams(block.blockIndex, rawInlineParams);
 92
 93    return {
 94      blockIndex: block.blockIndex,
 95      params: normalizedInlineParams.params,
 96      paramsKind: normalizedInlineParams.paramsKind,
 97      rawBlock: block.rawBlock,
 98      rawInstruction: instructionText,
 99      target,
100      tool
101    };
102  }
103
104  if (hasBody) {
105    return {
106      blockIndex: block.blockIndex,
107      params: rawBody!,
108      paramsKind: "body",
109      rawBlock: block.rawBlock,
110      rawInstruction: instructionText,
111      target,
112      tool
113    };
114  }
115
116  return {
117    blockIndex: block.blockIndex,
118    params: null,
119    paramsKind: "none",
120    rawBlock: block.rawBlock,
121    rawInstruction: instructionText,
122    target,
123    tool
124  };
125}