[
  {
    "content": "# Committee Design Specification\n\n## Overview\n\nCommittee is a system that facilitates structured, iterative design processes through simulated multi-stakeholder feedback. This specification defines the architecture and components used to create workflows that orchestrate tasks, manage context, and produce outputs.\n\n## Core Concepts\n\nCommittee uses a hierarchical structure composed of the following core concepts:\n\n1. **Task**: The atomic unit of work, typically involving a template for an LLM call\n2. **Set**: A collection of tasks or other sets, with a defined execution mode\n3. **Phase**: A major stage in a workflow with its own set of related tasks/sets\n4. **Workflow**: A complete process that orchestrates multiple phases\n\n## Component Definitions\n\n### Task\n\nA task is the basic unit of work in Committee. Tasks are typically template-driven and produce outputs that can be used by downstream components.\n\n```yaml\n# Example task definition in frontmatter\n---\nname: \"thinking-task\"\nrole: \"service\"\nrequiredInput: [\"directiveName\", \"serviceName\"]\nrequiredOutput: [\"thinking\"]\n---\n```\n\n#### Task Properties\n\n| Property | Description | Required |\n|----------|-------------|----------|\n| `name` | Unique identifier for the task | Yes |\n| `role` | Role to use for the task (e.g., \"service\", \"architect\") | No |\n| `requiredInput` | List of context variables required as input | No |\n| `requiredOutput` | List of context variables produced as output | No |\n\n### Set\n\nA set organizes related tasks or other sets with a defined execution mode. Sets can be executed sequentially or in parallel.\n\n```yaml\n# Example set definition\nname: \"service-feedback\"\ndescription: \"Collect feedback from a service\"\nexecution: \"sequential\"  # or \"parallel\"\ntasks:\n  - description: \"Think about requirements\"\n    useTask: \"thinking.task\"\n    variables:\n      role: \"service\"\n      \n  - description: \"Generate response\"\n    useTask: \"response.task\"\n    variables:\n      role: \"service\"\nrequiredInput: [\"directiveName\", \"serviceName\"]\nrequiredOutput: [\"serviceRequirements\"]\n```\n\n#### Set Properties\n\n| Property | Description | Required |\n|----------|-------------|----------|\n| `name` | Unique identifier for the set | Yes |\n| `description` | Human-readable description of the set's purpose | No |\n| `execution` | Mode of execution: \"sequential\" or \"parallel\" | Yes |\n| `tasks` | List of tasks to execute | Yes (if not using `set`) |\n| `set` | List of child sets to execute | Yes (if not using `tasks`) |\n| `requiredInput` | List of context variables required as input | No |\n| `requiredOutput` | List of context variables produced as output | No |\n| `for_each` | Collection to iterate over | No |\n| `variables` | Variables to set for each task | No |\n\n### Phase\n\nA phase represents a major stage in a workflow and contains a set of tasks or other sets.\n\n```yaml\n# Example phase definition\nname: \"requirements-collection\"\ndescription: \"Collect requirements from all services\"\nexecution: \"sequential\"\nhumanInputRequired: []  # No human input required for this phase\nset:\n  - description: \"Collect requirements from each service in parallel\"\n    template: \"service-feedback\"\n    execution: \"parallel\"\n    for_each: \"services\"\n    variables:\n      serviceName: \"{{item.name}}\"\n      serviceDescription: \"{{item.description}}\"\n    \n  - description: \"Synthesize all the collected requirements\"\n    useSet: \"architect-synthesis\"\n    requiredInput: [\"serviceRequirements\"]\n    requiredOutput: [\"synthesizedRequirements\"]\ndependsOn: []  # No dependencies for this phase\n```\n\n```yaml\n# Example phase with human input required\nname: \"draft-spec-review\"\ndescription: \"Review the draft specification\"\nhumanInputRequired:\n  - \"_output/draft-spec/draft-spec.o.md\"\nexecution: \"sequential\"\nset:\n  - description: \"Process human feedback on draft spec\"\n    useSet: \"feedback-processor\"\ndependsOn: [\"draft-spec-creation\"]\n```\n\n#### Phase Properties\n\n| Property | Description | Required |\n|----------|-------------|----------|\n| `name` | Unique identifier for the phase | Yes |\n| `description` | Human-readable description of the phase's purpose | No |\n| `execution` | Mode of execution: \"sequential\" or \"parallel\" | Yes |\n| `humanInputRequired` | List of files requiring human review before starting this phase | No |\n| `set` | List of sets to execute | Yes |\n| `dependsOn` | List of phases this phase depends on | No |\n| `requiredInput` | List of context variables required as input | No |\n| `requiredOutput` | List of context variables produced as output | No |\n| `condition` | Condition for executing this phase | No |\n\n### Workflow\n\nA workflow is the top-level container that orchestrates multiple phases to achieve a complete process.\n\n```yaml\n# Example workflow definition\nname: \"directive-review\"\ndescription: \"Review and design for a directive\"\noutputPath: \"_output/directive-review\"  # Base path for all outputs\nphases:\n  - usePhase: \"requirements-collection\"\n  - usePhase: \"requirements-synthesis\"\n    dependsOn: [\"requirements-collection\"]\n  - usePhase: \"draft-spec-creation\"\n    dependsOn: [\"requirements-synthesis\"]\n  - usePhase: \"draft-spec-review\"\n    dependsOn: [\"draft-spec-creation\"]\n```\n\n#### Workflow Properties\n\n| Property | Description | Required |\n|----------|-------------|----------|\n| `name` | Unique identifier for the workflow | Yes |\n| `description` | Human-readable description of the workflow's purpose | No |\n| `outputPath` | Base path for all workflow outputs | Yes |\n| `phases` | List of phases to execute | Yes |\n\n## Component References\n\nComponents can reference other components using a path-like syntax. The system resolves references using a specificity cascade:\n\n1. First looks in the current workflow's templates directory\n2. If not found, looks in the global templates directory\n3. If still not found, raises an error\n\nReference types:\n\n- `useTask`: Reference to a task template\n- `useSet`: Reference to a set template\n- `usePhase`: Reference to a phase template\n- `template`: Reference to a template that will be instantiated (potentially multiple times)\n\n## File Structure and Naming Conventions\n\n```\nworkflows/\n  ├── templates/                   # Global templates\n  │   ├── tasks/                   \n  │   │   ├── thinking.task.md     # Task template with frontmatter\n  │   │   ├── response.task.md     \n  │   │   └── human-review.task.md \n  │   │\n  │   ├── sets/                    \n  │   │   ├── two-phase-process.set.yaml\n  │   │   ├── service-feedback.set.yaml\n  │   │   └── synthesis.set.yaml\n  │   │\n  │   └── phases/                  \n  │       └── feedback-collection.phase.yaml\n  │\n  ├── directive-review/            # A specific workflow\n  │   ├── workflow.yaml            # Main workflow definition\n  │   │\n  │   ├── templates/               # Workflow-specific templates\n  │   │   ├── tasks/\n  │   │   │   └── directive-thinking.task.md\n  │   │   └── sets/\n  │   │       └── directive-feedback.set.yaml\n  │   │\n  │   └── phases/                  # Workflow-specific phases\n  │       ├── requirements.phase.yaml\n  │       ├── draft-spec.phase.yaml\n  │       └── final-spec.phase.yaml\n```\n\n### Naming Conventions\n\n- Task templates: `[name].task.md` or `[name].task.yaml`\n- Set templates: `[name].set.yaml`\n- Phase templates: `[name].phase.yaml`\n- Workflow definition: `workflow.yaml`\n\n## Output Path Standardization\n\nCommittee uses a standardized convention for output paths. All output files end with `.o.md` and follow this structure:\n\n```\n{workflow_path}/{phase}/{set}/{task}.o.md     # For task outputs\n{workflow_path}/{phase}/{set}.o.md            # For set outputs\n{workflow_path}/{phase}/{phase}.o.md          # For phase outputs\n```\n\nWhere `workflow_path` is defined in the workflow configuration. This standardization makes outputs predictable and easy to locate without the need for per-component output path configuration.\n\nFor example, a task output might be located at:\n```\n_output/directive-review/requirements-collection/service-feedback-parser/thinking.o.md\n```\n\n## Context Management\n\nCommittee manages context throughout the workflow execution:\n\n1. Each component declares what context it requires (`requiredInput`) and what it produces (`requiredOutput`)\n2. Context flows from upstream components to downstream components based on dependencies\n3. Components can access context variables using `{{variableName}}` syntax in templates\n4. For parallel execution, all required context must be available at the start\n5. For sequential execution, context can flow between steps\n\n## State Management\n\nCommittee uses a state file (`resume.yaml`) to track execution progress. This file is append-only to simplify state management and avoid race conditions:\n\n```yaml\n# Simplified resume.yaml example\nawaitingHumanInput: true\nfilesAwaitingReview:\n  - \"_output/directive-review/draft-spec/draft-spec.o.md\"\n  \n# Simple list of completed sets (append-only)\nprogress:\n  - phaseId: \"requirements-collection\"\n    setId: \"service-feedback-parser-service\"\n  - phaseId: \"requirements-collection\"\n    setId: \"service-feedback-validator-service\"\n  - phaseId: \"requirements-collection\"\n    setId: \"requirements-synthesis\"\n```\n\nThis state file enables:\n1. Resuming execution after interruption (at the set level)\n2. Tracking human review requirements\n3. Recording progress without complex state management\n\n## Human Input Handling\n\nHuman input is managed at phase boundaries rather than as a specific task type. When a phase completes, the system checks if the next phase requires human input (`humanInputRequired` property). If so:\n\n1. The system adds the required files to `filesAwaitingReview` in the resume file\n2. Sets `awaitingHumanInput` to true\n3. The CLI exits with a message indicating which files need review\n4. When the user marks the files as reviewed, the system will continue with the next phase\n\nThis approach ensures human interaction happens at well-defined points in the workflow rather than arbitrarily within phases.\n\n> **Note**: If a workflow needs multiple human interaction points within what would conceptually be a single phase, it is recommended to split it into multiple phases with naming conventions like `phase-1` and `phase-2` to indicate their relationship.\n\n## CLI Commands\n\nCommittee provides a command-line interface for interacting with workflows:\n\n- `cmte <workflow-folder>`: Start or resume a workflow\n- `cmte next <workflow-folder>`: Run the next task in a workflow\n- `cmte review <workflow-folder> <file-path>`: Mark a file as reviewed\n- `cmte init <workflow-folder>`: Initialize a workflow without running any tasks\n\n## Example: Complete Workflow\n\nHere's a complete example of a simple requirements collection workflow:\n\n### Workflow Definition\n\n```yaml\n# workflows/simple-requirements/workflow.yaml\nname: \"simple-requirements\"\ndescription: \"Collect and synthesize requirements\"\noutputPath: \"_output/simple-requirements\"\nphases:\n  - usePhase: \"requirements-collection\"\n  - usePhase: \"requirements-synthesis\"\n    dependsOn: [\"requirements-collection\"]\n  - usePhase: \"human-review\"\n    dependsOn: [\"requirements-synthesis\"]\n    humanInputRequired:\n      - \"_output/simple-requirements/requirements-synthesis/requirements-synthesis.o.md\"\n```\n\n### Phase Definition\n\n```yaml\n# workflows/simple-requirements/phases/requirements-collection.phase.yaml\nname: \"requirements-collection\"\ndescription: \"Collect requirements from all services\"\nexecution: \"sequential\"\nhumanInputRequired: []\nset:\n  - description: \"Collect requirements from each service in parallel\"\n    template: \"service-feedback\"\n    execution: \"parallel\"\n    for_each: \"services\"\n    variables:\n      serviceName: \"{{item.name}}\"\n      serviceDescription: \"{{item.description}}\"\n    requiredInput: [\"directiveName\"]\n    requiredOutput: [\"serviceRequirements\"]\n```\n\n### Set Template\n\n```yaml\n# workflows/templates/sets/service-feedback.set.yaml\nname: \"service-feedback\"\ndescription: \"Collect feedback from a service\"\nexecution: \"sequential\"\ntasks:\n  - description: \"Think about requirements\"\n    useTask: \"thinking.task\"\n    variables:\n      role: \"service\"\n      \n  - description: \"Generate response\"\n    useTask: \"response.task\"\n    variables:\n      role: \"service\"\nrequiredInput: [\"directiveName\", \"serviceName\"]\nrequiredOutput: [\"serviceRequirements\"]\n```\n\n### Task Template\n\n```markdown\n# workflows/templates/tasks/thinking.task.md\n---\nname: \"thinking-task\"\nrole: \"{{role}}\"\nrequiredInput: [\"directiveName\", \"serviceName\"]\nrequiredOutput: [\"thinking\"]\n---\n\n# {{directiveName}} Type Requirements Analysis - Thinking Phase\n\n## Context\nThis is an example template for the requirements thinking phase.\n\n## Service Context\nYou are the lead developer for {{serviceName}}, which is responsible for {{serviceDescription}}.\n\n## Task\nThink about what your service needs from the {{directiveName}} type system. Consider:\n\n1. What properties must exist in {{directiveName}} types?\n2. What pain points exist in the current implementation?\n3. How would more structured types improve your service's code?\n4. What type discriminators would make processing more robust?\n\n## Reflection\nBefore providing your final answer, reflect on:\n- Essential vs. nice-to-have properties\n- Implementation complexity\n- Cross-service impacts\n- Concrete use cases\n```\n\n## Implementation Considerations\n\nWhen implementing the Committee system:\n\n1. **Component Registry**: Create a registry that loads and indexes all components\n2. **Context Manager**: Implement a robust context management system that tracks variables\n3. **Executor**: Build executors for different execution modes (sequential, parallel)\n4. **Template Rendering**: Use a template engine that supports variable substitution\n5. **State Management**: Implement append-only state tracking for simplicity and robustness\n6. **Output Path Generation**: Generate standardized output paths based on workflow structure\n7. **Human Input Handling**: Manage human input at phase boundaries\n8. **Path Resolution**: Implement the specificity cascade for component references\n\n## Benefits of This Design\n\n1. **Composability**: Components can be composed and reused across workflows\n2. **Clarity**: Clear separation of concerns between different component types\n3. **Flexibility**: Support for both sequential and parallel execution\n4. **Maintainability**: Templates and their configurations are kept together\n5. **Predictability**: Standardized output paths make results easy to locate\n6. **Simplicity**: Append-only state management eliminates complex state tracking\n7. **Human Integration**: Clear, phase-boundary integration points for human review ",
    "filename": "SPEC.md"
  },
  {
    "content": "# {{directiveName}} Type Requirements - Response Phase\n\n## Context\nYou are finalizing your requirements for {{directiveName}} types.\n\n## Service Context\nYou are the lead developer for {{serviceName}}, which is responsible for {{serviceDescription}}.\n\n## Previous Thinking\nYou previously analyzed the requirements and came to these conclusions:\n\n{{thinking}}\n\n## Task\nBased on your previous analysis, create a formal list of requirements for {{directiveName}} types from your service's perspective:\n\n1. Provide a prioritized list of properties that should be included in {{directiveName}} types\n2. Specify any type discriminators that would be helpful\n3. Highlight integration points with other services\n4. Suggest validation rules for these types\n\n## Response Format\nStructure your response with clear headings and bullet points for each requirement category. Include code examples where appropriate to illustrate your points. Be concise but thorough. ",
    "filename": "_temp/requirements-collection/requirements-response/requirements-response.template.md"
  },
  {
    "content": "import yaml from 'js-yaml';\nimport { readFile, fileExists } from '../utils/fs.ts';\nimport logger from '../utils/logger.ts';\nimport { Configuration } from './types.ts';\n\n/**\n * Validates the configuration object\n * @param config Configuration object\n * @throws Error if validation fails\n */\nfunction validateConfig(config: any): void {\n  // Check required top-level properties\n  const requiredProps = ['project', 'directives', 'services', 'ai', 'process'];\n  for (const prop of requiredProps) {\n    if (!config[prop]) {\n      throw new Error(`Missing required configuration property: ${prop}`);\n    }\n  }\n\n  // Check project configuration\n  if (!config.project.name) {\n    throw new Error('Project name is required');\n  }\n  if (!config.project.outputDirectory) {\n    throw new Error('Project output directory is required');\n  }\n\n  // Check directives\n  if (!Array.isArray(config.directives) || config.directives.length === 0) {\n    throw new Error('At least one directive type must be defined');\n  }\n\n  // Check services\n  if (!Array.isArray(config.services) || config.services.length === 0) {\n    throw new Error('At least one service must be defined');\n  }\n\n  // Check AI configuration\n  if (!config.ai.provider) {\n    throw new Error('AI provider is required');\n  }\n  if (!config.ai.defaultModel) {\n    throw new Error('Default AI model is required');\n  }\n\n  // Check process flows\n  if (!Array.isArray(config.process.flows) || config.process.flows.length === 0) {\n    throw new Error('At least one process flow must be defined');\n  }\n\n  // Validate each flow has phases\n  for (const flow of config.process.flows) {\n    if (!flow.name) {\n      throw new Error('All flows must have a name');\n    }\n    if (!Array.isArray(flow.phases) || flow.phases.length === 0) {\n      throw new Error(`Flow \"${flow.name}\" must have at least one phase`);\n    }\n  }\n\n  logger.debug('Configuration validation passed');\n}\n\n/**\n * Loads and validates the configuration from a YAML file\n * @param configPath Path to the configuration file\n * @returns Validated configuration object\n */\nexport async function loadConfig(configPath: string): Promise<Configuration> {\n  logger.info(`Loading configuration from ${configPath}`);\n  \n  // Check if the file exists\n  if (!(await fileExists(configPath))) {\n    throw new Error(`Configuration file not found: ${configPath}`);\n  }\n  \n  try {\n    // Read the YAML file\n    const yamlContent = await readFile(configPath);\n    \n    // Parse YAML\n    const config = yaml.load(yamlContent) as Configuration;\n    \n    // Validate the configuration\n    validateConfig(config);\n    \n    logger.info('Configuration loaded successfully');\n    return config;\n  } catch (error) {\n    if (error instanceof yaml.YAMLException) {\n      logger.error('Invalid YAML format', { error });\n      throw new Error(`Invalid YAML format in ${configPath}: ${(error as yaml.YAMLException).message}`);\n    } else if (error instanceof Error) {\n      logger.error('Failed to load configuration', { error });\n      throw error;\n    } else {\n      // For any other unknown error types\n      logger.error('Unknown error loading configuration', { error });\n      throw new Error(`Unknown error loading configuration: ${String(error)}`);\n    }\n  }\n}\n\n/**\n * Creates a default configuration object\n * @returns Default configuration\n */\nexport function createDefaultConfig(): Configuration {\n  return {\n    project: {\n      name: 'Mlld Type System Redesign',\n      description: 'Comprehensive redesign of the mlld type system',\n      outputDirectory: '_dev/cleanup',\n    },\n    directives: [\n      {\n        name: 'variables',\n        description: 'Variable reference type system',\n      }\n    ],\n    services: [\n      {\n        name: 'ParserService',\n        description: 'Responsible for parsing source files into AST nodes',\n      }\n    ],\n    ai: {\n      provider: 'anthropic',\n      defaultModel: 'claude-3-7-sonnet-latest',\n      apiKeyEnv: 'ANTHROPIC_API_KEY',\n    },\n    process: {\n      flows: [\n        {\n          name: 'directive-review',\n          description: 'Review and design types for a specific directive',\n          phases: [\n            {\n              name: 'requirements-collection',\n              description: 'Collect service requirements for directive',\n              processType: 'feedback-collector',\n              params: {\n                thinkingPromptTemplate: 'templates/requirements-thinking.md',\n                responsePromptTemplate: 'templates/requirements-response.md',\n                outputDir: '_dev/cleanup/{{directiveName}}/requirements',\n              }\n            }\n          ]\n        }\n      ]\n    }\n  };\n} ",
    "filename": "config/loader.ts"
  },
  {
    "content": "// Configuration Type Definitions\n\n/**\n * Principle represents a architectural principle to follow\n */\nexport interface Principle {\n  name: string;\n  description: string;\n}\n\n/**\n * Directive Type represents a type of directive to analyze\n */\nexport interface DirectiveType {\n  name: string;\n  description: string;\n  clarityDoc?: string;\n  subtypes?: string[];\n  additionalContext?: string;\n}\n\n/**\n * Service represents a service that uses the directive types\n */\nexport interface Service {\n  name: string;\n  description: string;\n  documents?: string[];\n  code?: string[];\n  [key: string]: any; // Allow for additional properties\n}\n\n/**\n * PhaseConfig represents the configuration for a process phase\n */\nexport interface PhaseConfig {\n  name: string;\n  description: string;\n  processType: 'feedback-collector' | 'architect-processor' | 'human-review' |\n               'service-planning' | 'cross-team-review' | 'pm-processor';\n  dependsOn?: string[];\n  params: {\n    thinkingPromptTemplate?: string;\n    responsePromptTemplate?: string;\n    promptTemplate?: string;\n    outputPath?: string | {\n      thinking?: string;\n      response?: string;\n      [key: string]: string | undefined;\n    };\n    outputDir?: string;\n    [key: string]: any;\n  };\n  required?: boolean;\n  condition?: string; // String representation of a condition function\n}\n\n/**\n * Flow represents a process flow with phases\n */\nexport interface Flow {\n  name: string;\n  description: string;\n  phases: PhaseConfig[];\n}\n\n/**\n * AIConfig represents AI provider configuration\n */\nexport interface AIConfig {\n  provider: string;\n  defaultModel: string;\n  thinkingModel?: string;\n  responseModel?: string;\n  apiKeyEnv: string;\n  maxTokens?: number;\n  temperature?: number;\n}\n\n/**\n * OrchestrationConfig represents process orchestration settings\n */\nexport interface OrchestrationConfig {\n  parallelDirectives?: boolean;\n  parallelServices?: boolean;\n  maxConcurrency?: number;\n  resumable?: boolean;\n  humanReviewHandling?: 'pause-until-review' | 'skip' | 'simulate';\n}\n\n/**\n * The complete configuration object\n */\nexport interface Configuration {\n  project: {\n    name: string;\n    description: string;\n    outputDirectory: string;\n    principles?: Principle[];\n  };\n  directives: DirectiveType[];\n  services: Service[];\n  ai: AIConfig;\n  process: {\n    flows: Flow[];\n    orchestration?: OrchestrationConfig;\n  };\n} ",
    "filename": "config/types.ts"
  },
  {
    "content": "import { Anthropic } from '@anthropic-ai/sdk';\nimport { logger } from '../utils/logger.js';\nimport * as llmxml from '../utils/llmxml.ts';\nimport path from 'path';\nimport fs from 'fs/promises';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\nimport { config } from 'dotenv';\nimport { loadEnvVars } from '../utils/env.js';\n\n// Define the message types and roles\nexport type MessageRole = 'user' | 'assistant';\n\nexport interface Message {\n  role: MessageRole;\n  content: string;\n}\n\nexport interface ModelConfig {\n  model?: string;\n  temperature?: number;\n  maxTokens?: number;\n  useXML?: boolean; // New field for XML formatting\n  savePrompt?: boolean; // Whether to save the prompt to a file\n  dryRun?: boolean; // Whether to skip the actual API call\n  apiDryRun?: boolean; // Whether to use compressed prompts and one-sentence responses\n  useHaikuModel?: boolean; // Whether to use small, inexpensive models\n  outputPath?: string; // Path to save the response (used to determine prompt path)\n}\n\n// Smaller, inexpensive model for haiku mode\nconst HAIKU_MODEL = 'claude-3-haiku-20240307';\n\n/**\n * Claude API client for interacting with Anthropic's Claude models\n */\nexport class ClaudeClient {\n  private anthropic: Anthropic | null = null;\n  private defaultModel: string;\n  private defaultMaxTokens: number;\n  private defaultTemperature: number;\n  private useXML: boolean;\n  private isTestMode: boolean;\n  private static instance: ClaudeClient | null = null;\n\n  /**\n   * Creates a new Claude API client\n   */\n  private constructor() {\n    this.isTestMode = process.env.NODE_ENV === 'test';\n    \n    // Ensure environment variables are loaded\n    loadEnvVars();\n    \n    const apiKey = process.env.ANTHROPIC_API_KEY;\n    \n    if (!apiKey && !this.isTestMode) {\n      throw new Error('ANTHROPIC_API_KEY environment variable is required');\n    }\n\n    if (apiKey) {\n      this.anthropic = new Anthropic({\n        apiKey,\n      });\n    }\n\n    this.defaultModel = process.env.DEFAULT_MODEL || 'claude-3-7-sonnet-latest';\n    this.defaultMaxTokens = parseInt(process.env.MAX_TOKENS || '4000', 10);\n    this.defaultTemperature = parseFloat(process.env.TEMPERATURE || '0.7');\n    this.useXML = process.env.USE_XML === 'true';\n\n    logger.debug('Claude client initialized', {\n      defaultModel: this.defaultModel,\n      defaultMaxTokens: this.defaultMaxTokens,\n      defaultTemperature: this.defaultTemperature,\n      useXML: this.useXML,\n      isTestMode: this.isTestMode\n    });\n  }\n\n  /**\n   * Gets the singleton instance of the Claude client\n   */\n  public static getInstance(): ClaudeClient {\n    if (!ClaudeClient.instance) {\n      ClaudeClient.instance = new ClaudeClient();\n    }\n    return ClaudeClient.instance;\n  }\n\n  /**\n   * Save a prompt to a file\n   * @param prompt The prompt content to save\n   * @param outputPath The output path for the response (used to determine prompt filename)\n   */\n  private async savePromptToFile(prompt: string, outputPath?: string): Promise<void> {\n    if (!outputPath) {\n      logger.warn('Cannot save prompt: output path not provided');\n      return;\n    }\n    \n    try {\n      // Create the prompt file path by changing the extension\n      // Example: for \"path/to/response.o.xml\" -> \"path/to/response.sent.xml\"\n      const promptPath = outputPath.replace(/\\.o\\.xml$|\\.o\\.md$/, '.sent.xml');\n      \n      if (promptPath === outputPath) {\n        // If no replacement was made, append .sent.xml to the path\n        const parsedPath = path.parse(outputPath);\n        const promptPathWithExt = path.join(\n          parsedPath.dir, \n          `${parsedPath.name}.sent.xml`\n        );\n        await this.saveFileWithDir(promptPathWithExt, prompt);\n      } else {\n        await this.saveFileWithDir(promptPath, prompt);\n      }\n      \n      logger.info(`Saved prompt to ${promptPath}`);\n    } catch (error) {\n      logger.error('Failed to save prompt file', { error, outputPath });\n    }\n  }\n  \n  /**\n   * Save a file, creating directories if they don't exist\n   * @param filePath The file path\n   * @param content The content to save\n   */\n  private async saveFileWithDir(filePath: string, content: string): Promise<void> {\n    await fs.mkdir(path.dirname(filePath), { recursive: true });\n    await fs.writeFile(filePath, content);\n  }\n\n  /**\n   * Creates a compressed version of a prompt for API dry run mode\n   * @param content The original prompt content\n   * @returns Compressed prompt with only structure and metadata\n   */\n  private async createCompressedPrompt(content: string): Promise<string> {\n    // Get the first 200 characters to show the prompt structure\n    const promptPreview = content.substring(0, 200);\n    \n    // Extract headings to understand the structure\n    const headings: string[] = [];\n    const headingRegex = /^#+\\s+(.+)$/gm;\n    let match;\n    while ((match = headingRegex.exec(content)) !== null) {\n      headings.push(match[1]);\n    }\n    \n    // Count code blocks and their languages\n    const codeBlocks: Record<string, number> = {};\n    const codeBlockRegex = /```([a-zA-Z0-9]*)/g;\n    while ((match = codeBlockRegex.exec(content)) !== null) {\n      const language = match[1] || 'text';\n      codeBlocks[language] = (codeBlocks[language] || 0) + 1;\n    }\n    \n    // Extract variables used in the prompt\n    const variables: string[] = [];\n    const variableRegex = /\\{\\{([^}]+)\\}\\}/g;\n    while ((match = variableRegex.exec(content)) !== null) {\n      variables.push(match[1]);\n    }\n    \n    // Build a structured compressed prompt\n    const compressed = [\n      \"# Compressed Prompt for API Dry Run\",\n      \"\",\n      \"## Prompt Preview\",\n      \"```\",\n      promptPreview + (content.length > 200 ? \"...\" : \"\"),\n      \"```\",\n      \"\",\n      \"## Prompt Structure\",\n      `- Total length: ${content.length} characters`,\n      `- Sections: ${headings.length}`,\n      headings.length > 0 ? \"- Section headings: \" + headings.join(\", \") : \"\",\n      \"\",\n      \"## Code Blocks\",\n      Object.entries(codeBlocks).length > 0 \n        ? Object.entries(codeBlocks).map(([lang, count]) => `- ${lang || \"plain\"}: ${count}`).join(\"\\n\") \n        : \"- No code blocks\",\n      \"\",\n      \"## Variables\",\n      variables.length > 0 \n        ? variables.map(v => `- ${v}`).join(\"\\n\") \n        : \"- No variables\",\n      \"\",\n      \"## Instructions for Model\",\n      \"Please provide a one-sentence response identifying what this prompt appears to be asking for.\",\n    ].join(\"\\n\");\n    \n    return compressed;\n  }\n\n  /**\n   * Completes a conversation with Claude\n   * @param messages Array of message objects with role and content\n   * @param config Optional model configuration\n   * @returns Claude's response text\n   */\n  async completeMessages(\n    messages: Message[],\n    config?: ModelConfig\n  ): Promise<string> {\n    if (this.isTestMode) {\n      logger.warn('Claude client is in test mode - real API calls are disabled');\n      return \"This is a test mode response. Override 'think' and 'respond' methods for custom test responses.\";\n    }\n    \n    if (!this.anthropic && !config?.dryRun) {\n      throw new Error('Claude client is not initialized with API key');\n    }\n    \n    const model = config?.useHaikuModel \n      ? HAIKU_MODEL \n      : (config?.model || this.defaultModel);\n    const maxTokens = config?.maxTokens || this.defaultMaxTokens;\n    const temperature = config?.temperature || this.defaultTemperature;\n    const useXML = config?.useXML !== undefined ? config?.useXML : this.useXML;\n    const dryRun = config?.dryRun || false;\n    const apiDryRun = config?.apiDryRun || false;\n    const savePrompt = config?.savePrompt || dryRun || apiDryRun; // Always save prompts in dry run modes\n    const outputPath = config?.outputPath;\n\n    logger.debug('Configuring request to Claude API', {\n      model,\n      maxTokens,\n      temperature,\n      useXML,\n      dryRun,\n      apiDryRun,\n      useHaikuModel: config?.useHaikuModel,\n      savePrompt,\n      messageCount: messages.length,\n    });\n\n    try {\n      // Process messages with LLMXML if enabled and not in API dry run mode\n      const processedMessages = useXML && !apiDryRun\n        ? await Promise.all(messages.map(async msg => {\n            if (msg.role === 'user') {\n              const xmlContent = await llmxml.toXML(msg.content);\n              \n              // Debug log the XML content (only first 500 chars for brevity)\n              logger.debug(`XML formatted prompt (first 500 chars): ${xmlContent.substring(0, 500)}...`);\n              \n              // Write full content to debug file for inspection\n              try {\n                const debugDir = 'debug';\n                await fs.mkdir(debugDir, { recursive: true });\n                await fs.writeFile(`${debugDir}/last-claude-prompt.xml`, xmlContent);\n                logger.debug('Full XML prompt saved to debug/last-claude-prompt.xml');\n              } catch (e) {\n                logger.warn('Could not save debug file', e);\n              }\n              \n              return {\n                role: msg.role, \n                content: xmlContent\n              };\n            }\n            return msg;\n          }))\n        : messages;\n      \n      // Handle API dry run mode\n      let finalMessages = processedMessages;\n      if (apiDryRun) {\n        // Create compressed prompt for API dry run\n        finalMessages = await Promise.all(messages.map(async msg => {\n          if (msg.role === 'user') {\n            const compressedPrompt = await this.createCompressedPrompt(msg.content);\n            return {\n              role: msg.role,\n              content: compressedPrompt\n            };\n          }\n          return msg;\n        }));\n        \n        // Set a smaller max tokens for API dry run\n        const apiDryRunMaxTokens = 100;\n        logger.info(`API dry run: Using compressed prompt and limiting response to ${apiDryRunMaxTokens} tokens`);\n      }\n      \n      // Save prompt if requested\n      if (savePrompt && finalMessages.length > 0) {\n        await this.savePromptToFile(\n          finalMessages[0].content, \n          outputPath\n        );\n      }\n\n      // In dry run mode, don't make the API call\n      if (dryRun) {\n        logger.info('DRY RUN MODE: Skipping API call to Claude');\n        return \"DRY RUN MODE: This is a placeholder response. No API call was made.\";\n      }\n\n      // In haiku mode, log the model being used\n      if (config?.useHaikuModel) {\n        logger.info(`HAIKU MODE: Using smaller model (${HAIKU_MODEL}) for faster, less expensive results`);\n      }\n\n      // Initialize Anthropic client if not already initialized\n      if (!this.anthropic) {\n        if (!process.env.ANTHROPIC_API_KEY) {\n          throw new Error('ANTHROPIC_API_KEY environment variable is not set');\n        }\n        this.anthropic = new Anthropic({\n          apiKey: process.env.ANTHROPIC_API_KEY,\n        });\n      }\n\n      // Make the API call with the processed messages using streaming\n      const stream = await this.anthropic.messages.create({\n        model,\n        max_tokens: apiDryRun ? 100 : maxTokens,\n        temperature,\n        messages: finalMessages.map(msg => ({\n          role: msg.role,\n          content: msg.content,\n        })),\n        stream: true,\n      });\n\n      logger.debug('Started streaming response from Claude API');\n\n      // Collect the response chunks\n      let responseText = '';\n      let chunkCount = 0;\n      for await (const chunk of stream) {\n        if (chunk.type === 'content_block_delta' && chunk.delta?.type === 'text_delta') {\n          const text = chunk.delta.text;\n          responseText += text;\n          chunkCount++;\n          if (chunkCount % 10 === 0) { // Log every 10 chunks to avoid spam\n            logger.info('Received API chunk', {\n              chunkNumber: chunkCount,\n              chunkLength: text.length,\n              totalLength: responseText.length,\n              chunkPreview: text.substring(0, 50)\n            });\n          }\n        }\n      }\n\n      logger.info('Completed API response', {\n        totalChunks: chunkCount,\n        totalLength: responseText.length,\n        responsePreview: responseText.substring(0, 200),\n        model,\n      });\n\n      // Debug log the response\n      logger.debug(`Response (first 500 chars): ${responseText.substring(0, 500)}...`);\n      \n      // Write full response to debug file\n      try {\n        const debugDir = 'debug';\n        await fs.mkdir(debugDir, { recursive: true });\n        await fs.writeFile(`${debugDir}/last-claude-response.txt`, responseText);\n        logger.debug('Full response saved to debug/last-claude-response.txt');\n      } catch (e) {\n        logger.warn('Could not save debug file', e);\n      }\n\n      // For API dry run mode, return the response as is (it's already a simplified response)\n      if (apiDryRun) {\n        return responseText;\n      }\n\n      // Convert XML back to markdown if needed (and not in API dry run mode)\n      if (useXML && !apiDryRun) {\n        try {\n          logger.debug('Converting XML response to Markdown');\n          const markdown = await llmxml.toMarkdown(responseText);\n          \n          // Save converted markdown for debugging\n          try {\n            await fs.writeFile('debug/last-claude-response-markdown.md', markdown);\n            logger.debug('Converted markdown saved to debug/last-claude-response-markdown.md');\n          } catch (e) {\n            logger.warn('Could not save debug file', e);\n          }\n          \n          return markdown;\n        } catch (error) {\n          logger.warn('Failed to convert XML response to Markdown, returning as is', { error });\n          return responseText;\n        }\n      }\n      \n      return responseText;\n    } catch (error) {\n      logger.error('Error calling Claude API', { error });\n      throw new Error(`Claude API error: ${(error as Error).message}`);\n    }\n  }\n\n  /**\n   * Sends a prompt to Claude and returns the response\n   * @param prompt The prompt text\n   * @param config Optional model configuration\n   * @returns Claude's response text\n   */\n  async completePrompt(prompt: string, config?: ModelConfig): Promise<string> {\n    const messages: Message[] = [\n      {\n        role: 'user',\n        content: prompt,\n      },\n    ];\n\n    return this.completeMessages(messages, config);\n  }\n\n  /**\n   * Simplified method for thinking-phase prompts\n   * @param prompt The thinking prompt\n   * @param config Optional model configuration\n   * @returns Claude's thinking response\n   */\n  async think(prompt: string, config?: ModelConfig): Promise<string> {\n    const thinkingModel = process.env.THINKING_MODEL || this.defaultModel;\n    return this.completePrompt(prompt, {\n      model: thinkingModel,\n      ...config,\n    });\n  }\n\n  /**\n   * Simplified method for response-phase prompts\n   * @param prompt The response prompt\n   * @param config Optional model configuration\n   * @returns Claude's response\n   */\n  async respond(prompt: string, config?: ModelConfig): Promise<string> {\n    const responseModel = process.env.RESPONSE_MODEL || this.defaultModel;\n    return this.completePrompt(prompt, {\n      model: responseModel,\n      ...config,\n    });\n  }\n}\n\n// Export a function to get the singleton instance\nexport default function getClaudeClient(): ClaudeClient {\n  return ClaudeClient.getInstance();\n} ",
    "filename": "core/claude.ts"
  },
  {
    "content": "import yaml from 'js-yaml';\nimport { Task } from './interfaces.js';\nimport { readFile } from '../../utils/fs.js';\nimport logger from '../../utils/logger.js';\n\n/**\n * Extracts frontmatter from a markdown file\n * \n * @param content Markdown content to parse\n * @returns Object with frontmatter metadata or null if not found\n */\nexport function extractFrontmatter(content: string): Record<string, any> | null {\n  const frontmatterRegex = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/;\n  const match = content.match(frontmatterRegex);\n  \n  if (!match) {\n    return null;\n  }\n  \n  try {\n    const frontmatterYaml = match[1];\n    return yaml.load(frontmatterYaml) as Record<string, any>;\n  } catch (error) {\n    logger.error('Error parsing frontmatter YAML', { error });\n    return null;\n  }\n}\n\n/**\n * Extracts the content after frontmatter\n * \n * @param content Markdown content to parse\n * @returns Content without frontmatter\n */\nexport function extractContentWithoutFrontmatter(content: string): string {\n  const frontmatterRegex = /^---\\s*\\n[\\s\\S]*?\\n---\\s*\\n/;\n  return content.replace(frontmatterRegex, '');\n}\n\n/**\n * Loads a task template file and parses its frontmatter\n * \n * @param filePath Path to the task template file\n * @returns Task metadata and template content\n */\nexport async function loadTaskFromFile(filePath: string): Promise<{ \n  task: Task; \n  content: string;\n}> {\n  try {\n    const fileContent = await readFile(filePath);\n    const frontmatter = extractFrontmatter(fileContent);\n    \n    if (!frontmatter) {\n      throw new Error(`No frontmatter found in task file: ${filePath}`);\n    }\n    \n    if (!frontmatter.name) {\n      throw new Error(`Task name is required in frontmatter: ${filePath}`);\n    }\n    \n    const task: Task = {\n      name: frontmatter.name,\n      description: frontmatter.description,\n      role: frontmatter.role,\n      requiredInput: frontmatter.requiredInput,\n      requiredOutput: frontmatter.requiredOutput\n    };\n    \n    const content = extractContentWithoutFrontmatter(fileContent);\n    \n    return { task, content };\n  } catch (error) {\n    logger.error(`Error loading task from file: ${filePath}`, { error });\n    throw error;\n  }\n} ",
    "filename": "core/components/frontmatter.ts"
  },
  {
    "content": "// Export interfaces\nexport { FileContext } from './interfaces.js';\nexport * from './interfaces.js';\n\n// Export frontmatter loader\nexport * from './frontmatter.js';\n\n// Export component registry\nexport * from './registry.js';\n\n// Export path utilities\nexport * from './path.js';\n\n/**\n * The components module provides the hierarchical component model for Committee.\n * \n * Key concepts:\n * \n * 1. Task - The atomic unit of work, typically involving a template for an LLM call\n * 2. Set - A collection of tasks or other sets, with a defined execution mode\n * 3. Phase - A major stage in a workflow with its own set of related tasks/sets\n * 4. Workflow - A complete process that orchestrates multiple phases\n * \n * The component registry handles path resolution using a specificity cascade:\n * 1. First looks in the current workflow's templates directory\n * 2. If not found, looks in the global templates directory\n * \n * The path generator standardizes output paths using the .o.md convention:\n * {workflow_path}/{phase}/{set}/{task}.o.md     # For task outputs\n * {workflow_path}/{phase}/{set}.o.md            # For set outputs\n * {workflow_path}/{phase}/{phase}.o.md          # For phase outputs\n */ ",
    "filename": "core/components/index.ts"
  },
  {
    "content": "/**\n * Core component interfaces for Committee's hierarchical component model\n */\n\n/**\n * The base component interface with common properties\n */\nexport interface Component {\n  name: string;\n  description?: string;\n  requiredInput?: string[];\n  requiredOutput?: string[];\n}\n\n/**\n * File collection context for components\n */\nexport interface FileContext {\n  [key: string]: string[]; // Collection name -> array of file paths\n}\n\n/**\n * Type for context variables\n */\nexport type Context = Record<string, any>;\n\n/**\n * Model configuration for LLM calls\n */\nexport interface ModelConfig {\n  temperature?: number;\n  maxTokens?: number;\n  topP?: number;\n  frequencyPenalty?: number;\n  presencePenalty?: number;\n}\n\n/**\n * Task is the atomic unit of work, typically involving a template for an LLM call\n */\nexport interface Task extends Component {\n  role?: \"service\" | \"architect\" | \"pm\";\n  phase?: string;\n  processType?: \"two-phase\" | \"human-review\";\n  content?: string;\n  context?: FileContext;\n  outputPath?: {\n    thinking?: string;\n    response?: string;\n  };\n  modelConfig?: ModelConfig;\n  inputPath?: string;\n  required?: boolean;\n  requiredInput?: string[];\n  requiredOutput?: string[];\n}\n\n/**\n * Task reference with variables\n */\nexport interface TaskReference {\n  description?: string;\n  useTask: string;\n  variables?: Record<string, string>;\n  context?: FileContext; // New context property for file collections\n  requiredInput?: string[];\n  requiredOutput?: string[];\n}\n\n/**\n * Set reference with variables\n */\nexport interface SetReference {\n  description?: string;\n  useSet: string;\n  variables?: Record<string, string>;\n  context?: FileContext; // New context property for file collections\n  requiredInput?: string[];\n  requiredOutput?: string[];\n}\n\n/**\n * Template instantiation with variables\n */\nexport interface TemplateReference {\n  description?: string;\n  template: string;\n  execution: \"sequential\" | \"parallel\";\n  for_each?: string;\n  variables?: Record<string, string>;\n  context?: FileContext; // New context property for file collections\n  requiredInput?: string[];\n  requiredOutput?: string[];\n}\n\n/**\n * Set organizes related tasks or other sets with a defined execution mode\n */\nexport interface Set extends Component {\n  execution: \"sequential\" | \"parallel\";\n  tasks?: TaskReference[];\n  set?: (SetReference | TemplateReference)[];\n  for_each?: string;\n  variables?: Record<string, string>;\n  context?: FileContext; // New context property for file collections\n}\n\n/**\n * Phase reference with dependencies\n */\nexport interface PhaseReference {\n  usePhase: string;\n  dependsOn?: string[];\n  humanInputRequired?: string[];\n  context?: FileContext; // New context property for file collections\n  condition?: string;\n  for_each?: string;\n  variables?: Record<string, any>;\n}\n\n/**\n * Phase represents a major stage in a workflow\n */\nexport interface Phase extends Component {\n  execution: \"sequential\" | \"parallel\";\n  humanInputRequired?: string[];\n  set: (SetReference | TemplateReference)[];\n  dependsOn?: string[];\n  context?: FileContext; // New context property for file collections\n  condition?: string;\n  variables?: Record<string, any>;\n}\n\n/**\n * Workflow is the top-level container that orchestrates multiple phases\n */\nexport interface Workflow extends Component {\n  outputPath: string;\n  phases: PhaseReference[];\n  context?: FileContext;\n  variables?: Record<string, any>; // Add support for workflow-level variables\n} ",
    "filename": "core/components/interfaces.ts"
  },
  {
    "content": "import path from 'path';\nimport { ensureDir } from '../../utils/fs.js';\nimport { ComponentRegistry, ComponentType } from './index.js';\n\n/**\n * Output path generator for standardizing paths according to the .o.md convention\n */\nexport class PathGenerator {\n  private baseOutputPath: string;\n  \n  constructor(baseOutputPath: string) {\n    this.baseOutputPath = baseOutputPath;\n  }\n  \n  /**\n   * Generate a task output path\n   * \n   * @param phaseName Phase name\n   * @param phaseIteration Optional phase iteration name\n   * @param setName Set name\n   * @param setIteration Optional set iteration name\n   * @param taskName Task name\n   * @param taskIteration Optional task iteration name\n   * @returns Standardized task output path\n   */\n  generateTaskOutputPath(\n    phaseName: string,\n    phaseIteration: string | undefined,\n    setName: string,\n    setIteration: string | undefined,\n    taskName: string,\n    taskIteration?: string\n  ): string {\n    const parts = [phaseName];\n    \n    if (phaseIteration) {\n      parts.push(phaseIteration);\n    }\n    \n    parts.push(setName);\n    \n    if (setIteration) {\n      parts.push(setIteration);\n    }\n    \n    parts.push(taskName);\n    \n    if (taskIteration) {\n      parts.push(taskIteration);\n    }\n    \n    return path.join(this.baseOutputPath, ...parts, `${taskName}.o.md`);\n  }\n  \n  /**\n   * Generate a set output path\n   * \n   * @param phaseName Phase name\n   * @param phaseIteration Optional phase iteration name\n   * @param setName Set name\n   * @param setIteration Optional set iteration name\n   * @returns Standardized set output path\n   */\n  generateSetOutputPath(\n    phaseName: string,\n    phaseIteration: string | undefined,\n    setName: string,\n    setIteration?: string\n  ): string {\n    const parts = [phaseName];\n    \n    if (phaseIteration) {\n      parts.push(phaseIteration);\n    }\n    \n    parts.push(setName);\n    \n    if (setIteration) {\n      parts.push(setIteration);\n    }\n    \n    return path.join(this.baseOutputPath, ...parts, `${setName}.o.md`);\n  }\n  \n  /**\n   * Generate a phase output path\n   * \n   * @param phaseName Phase name\n   * @param phaseIteration Optional phase iteration name\n   * @returns Standardized phase output path\n   */\n  generatePhaseOutputPath(\n    phaseName: string,\n    phaseIteration?: string\n  ): string {\n    const parts = [phaseName];\n    \n    if (phaseIteration) {\n      parts.push(phaseIteration);\n    }\n    \n    return path.join(this.baseOutputPath, ...parts, `${phaseName}.o.md`);\n  }\n  \n  /**\n   * Generate an output path for an iterated item\n   * \n   * @param phaseName Phase name\n   * @param setName Set name\n   * @param itemName Item identifier\n   * @param taskName Task name\n   * @returns Standardized output path for an iterated item\n   */\n  generateIteratedTaskOutputPath(\n    phaseName: string, \n    setName: string, \n    itemName: string, \n    taskName: string\n  ): string {\n    return path.join(\n      this.baseOutputPath,\n      phaseName,\n      setName,\n      itemName,\n      `${taskName}.o.md`\n    );\n  }\n  \n  /**\n   * Generate an output path for an iterated set\n   * \n   * @param phaseName Phase name\n   * @param setName Set name\n   * @param itemName Item identifier\n   * @returns Standardized output path for an iterated set\n   */\n  generateIteratedSetOutputPath(\n    phaseName: string, \n    setName: string, \n    itemName: string\n  ): string {\n    return path.join(\n      this.baseOutputPath,\n      phaseName,\n      setName,\n      `${itemName}.o.md`\n    );\n  }\n  \n  /**\n   * Ensure the directory for an output path exists\n   * \n   * @param outputPath Output file path\n   */\n  async ensureOutputDirectory(outputPath: string): Promise<void> {\n    const directory = path.dirname(outputPath);\n    await ensureDir(directory);\n  }\n} ",
    "filename": "core/components/path.ts"
  },
  {
    "content": "import path from 'path';\nimport fs from 'fs';\nimport yaml from 'js-yaml';\nimport { Task, Set, Phase, Workflow } from './interfaces.js';\nimport { loadTaskFromFile } from './frontmatter.js';\nimport logger from '../../utils/logger.js';\nimport { readFile, fileExists } from '../../utils/fs.js';\n\n/**\n * Component types supported by the registry\n */\nexport enum ComponentType {\n  TASK = 'task',\n  SET = 'set',\n  PHASE = 'phase',\n  WORKFLOW = 'workflow'\n}\n\n/**\n * File context type for resolving file paths\n */\nexport interface FileContext {\n  [key: string]: string[];\n}\n\n/**\n * Registry for managing and loading components\n */\nexport class ComponentRegistry {\n  private tasks: Map<string, { task: Task; content: string }> = new Map();\n  private sets: Map<string, Set> = new Map();\n  private phases: Map<string, Phase> = new Map();\n  private workflows: Map<string, Workflow> = new Map();\n  private basePath: string;\n  private workflowPath: string | null = null;\n  \n  constructor(basePath: string) {\n    this.basePath = basePath;\n  }\n  \n  /**\n   * Set the current workflow path for workflow-specific templates\n   */\n  setWorkflowPath(workflowPath: string): void {\n    this.workflowPath = workflowPath;\n  }\n  \n  /**\n   * Clear the current workflow path\n   */\n  clearWorkflowPath(): void {\n    this.workflowPath = null;\n  }\n  \n  /**\n   * Resolve a component path following the specificity cascade:\n   * 1. Look in the current workflow's templates directory\n   */\n  private resolveComponentPath(componentType: ComponentType, componentName: string, extension?: string): string | null {\n    const fileExtension = extension || this.getDefaultExtension(componentType);\n    const filename = `${componentName}.${componentType}${fileExtension}`;\n    \n    // Look in the workflow templates directory\n    if (this.workflowPath) {\n      const templatePath = path.join(\n        this.basePath,\n        this.workflowPath,\n        'templates',\n        `${componentType}s`,\n        filename\n      );\n      \n      if (fs.existsSync(templatePath)) {\n        return templatePath;\n      }\n    }\n    \n    // For backward compatibility, try other paths\n    \n    // Try examples/templates/ (old structure)\n    const oldGlobalPath = path.join(\n      this.basePath,\n      'examples',\n      'templates',\n      `${componentType}s`,\n      filename\n    );\n    \n    if (fs.existsSync(oldGlobalPath)) {\n      return oldGlobalPath;\n    }\n    \n    // Try templates/ (legacy structure)\n    const legacyPath = path.join(\n      this.basePath,\n      'templates',\n      `${componentType}s`,\n      filename\n    );\n    \n    if (fs.existsSync(legacyPath)) {\n      return legacyPath;\n    }\n    \n    return null;\n  }\n  \n  /**\n   * Get the default file extension for a component type\n   */\n  private getDefaultExtension(componentType: ComponentType): string {\n    switch (componentType) {\n      case ComponentType.TASK:\n        return '.md';\n      case ComponentType.SET:\n      case ComponentType.PHASE:\n      case ComponentType.WORKFLOW:\n        return '.yaml';\n      default:\n        return '.yaml';\n    }\n  }\n  \n  /**\n   * Load a task from the registry, loading from disk if not found\n   */\n  async loadTask(taskName: string): Promise<{ task: Task; content: string }> {\n    // Check if task is already loaded\n    const cachedTask = this.tasks.get(taskName);\n    if (cachedTask) {\n      return cachedTask;\n    }\n    \n    // Find task file\n    const taskPath = this.resolveComponentPath(ComponentType.TASK, taskName, '.md');\n    if (!taskPath) {\n      throw new Error(`Task not found: ${taskName}`);\n    }\n    \n    // Load and parse task file\n    const { task: taskMetadata, content } = await loadTaskFromFile(taskPath);\n    \n    // Create task object with metadata\n    const task: Task = {\n      ...taskMetadata,\n      name: taskName,\n      processType: taskMetadata.processType || 'two-phase',\n      role: taskMetadata.role || 'service',\n      required: taskMetadata.required !== false,\n      requiredInput: taskMetadata.requiredInput || [],\n      requiredOutput: taskMetadata.requiredOutput || []\n    };\n    \n    // Cache task\n    const taskData = { task, content };\n    this.tasks.set(taskName, taskData);\n    \n    return taskData;\n  }\n  \n  /**\n   * Load a set from a file and register it\n   */\n  async loadSet(setName: string): Promise<Set> {\n    // Check if already loaded\n    if (this.sets.has(setName)) {\n      return this.sets.get(setName)!;\n    }\n    \n    // Resolve path\n    const setPath = this.resolveComponentPath(ComponentType.SET, setName);\n    if (!setPath) {\n      throw new Error(`Set template not found: ${setName}`);\n    }\n    \n    // Load and parse set\n    const content = await readFile(setPath);\n    const set = yaml.load(content) as Set;\n    \n    // Resolve file paths in context if present\n    if (set.context) {\n      set.context = this.resolveFilePaths(set.context);\n    }\n    \n    // Resolve context in task references\n    if (set.tasks) {\n      for (const task of set.tasks) {\n        if (task.context) {\n          task.context = this.resolveFilePaths(task.context);\n        }\n      }\n    }\n    \n    // Resolve context in nested set references\n    if (set.set) {\n      for (const nestedSet of set.set) {\n        if ('context' in nestedSet) {\n          nestedSet.context = this.resolveFilePaths(nestedSet.context!);\n        }\n      }\n    }\n    \n    this.sets.set(setName, set);\n    \n    return set;\n  }\n  \n  /**\n   * Load a phase from a file and register it\n   */\n  async loadPhase(phaseName: string): Promise<Phase> {\n    // Check if already loaded\n    if (this.phases.has(phaseName)) {\n      return this.phases.get(phaseName)!;\n    }\n    \n    // Resolve path\n    const phasePath = this.resolveComponentPath(ComponentType.PHASE, phaseName);\n    if (!phasePath) {\n      throw new Error(`Phase template not found: ${phaseName}`);\n    }\n    \n    // Load and parse phase\n    const content = await readFile(phasePath);\n    const phase = yaml.load(content) as Phase;\n    \n    // Resolve file paths in context if present\n    if (phase.context) {\n      phase.context = this.resolveFilePaths(phase.context);\n    }\n    \n    // Resolve context in set references\n    for (const setRef of phase.set) {\n      if ('context' in setRef) {\n        setRef.context = this.resolveFilePaths(setRef.context!);\n      }\n    }\n    \n    this.phases.set(phaseName, phase);\n    \n    return phase;\n  }\n  \n  /**\n   * Load a workflow from a file and register it\n   */\n  async loadWorkflow(workflowPath: string): Promise<Workflow> {\n    const normalizedPath = workflowPath.endsWith('/') ? workflowPath.slice(0, -1) : workflowPath;\n    const workflowName = path.basename(normalizedPath);\n    \n    // Check if already loaded\n    if (this.workflows.has(workflowName)) {\n      logger.debug('Workflow already loaded', { workflowName });\n      return this.workflows.get(workflowName)!;\n    }\n    \n    // Set workflow path for template resolution\n    this.setWorkflowPath(normalizedPath);\n    \n    const workflowFilePath = path.join(this.basePath, normalizedPath, 'workflow.yaml');\n    logger.debug('Loading workflow file', { workflowFilePath });\n    \n    if (!await fileExists(workflowFilePath)) {\n      throw new Error(`Workflow file not found: ${workflowFilePath}`);\n    }\n    \n    // Load and parse workflow\n    const content = await readFile(workflowFilePath);\n    const workflow = yaml.load(content) as Workflow;\n    \n    // Add name if not present\n    workflow.name = workflow.name || workflowName;\n    \n    // Resolve file paths in context if present\n    if (workflow.context) {\n      workflow.context = this.resolveFilePaths(workflow.context, path.join(this.basePath, normalizedPath));\n    }\n    \n    // Resolve context in phase references\n    for (const phaseRef of workflow.phases) {\n      if ('context' in phaseRef) {\n        phaseRef.context = this.resolveFilePaths(phaseRef.context!, path.join(this.basePath, normalizedPath));\n      }\n    }\n    \n    logger.debug('Loaded workflow', {\n      name: workflow.name,\n      phases: workflow.phases.length,\n      context: workflow.context ? Object.keys(workflow.context) : []\n    });\n    this.workflows.set(workflowName, workflow);\n    \n    return workflow;\n  }\n  \n  /**\n   * Resolves file paths in a context object relative to basePath\n   * @param context File context object\n   * @param basePath Optional base path for relative paths\n   * @returns Resolved context with absolute paths\n   */\n  private resolveFilePaths(context: FileContext, basePath?: string): FileContext {\n    const resolvedContext: FileContext = {};\n    \n    for (const [key, paths] of Object.entries(context)) {\n      if (!Array.isArray(paths)) continue;\n      \n      resolvedContext[key] = paths.map((filePath: string) => {\n        if (path.isAbsolute(filePath)) {\n          return filePath;\n        }\n        return path.resolve(basePath || this.basePath, filePath);\n      });\n    }\n    \n    return resolvedContext;\n  }\n  \n  /**\n   * Get a registered task\n   */\n  getTask(taskName: string): { task: Task; content: string } | undefined {\n    return this.tasks.get(taskName);\n  }\n  \n  /**\n   * Get a registered set\n   */\n  getSet(setName: string): Set | undefined {\n    return this.sets.get(setName);\n  }\n  \n  /**\n   * Get a registered phase\n   */\n  getPhase(phaseName: string): Phase | undefined {\n    return this.phases.get(phaseName);\n  }\n  \n  /**\n   * Get a registered workflow\n   */\n  getWorkflow(workflowName: string): Workflow | undefined {\n    return this.workflows.get(workflowName);\n  }\n\n  /**\n   * Register a task in the registry\n   */\n  async addTask(name: string, task: Task, content: string): Promise<void> {\n    this.tasks.set(name, { task, content });\n  }\n\n  /**\n   * Register a set in the registry\n   */\n  async addSet(name: string, set: Set): Promise<void> {\n    this.sets.set(name, set);\n  }\n\n  /**\n   * Register a phase in the registry\n   */\n  async addPhase(name: string, phase: Phase): Promise<void> {\n    this.phases.set(name, phase);\n  }\n\n  /**\n   * Register a workflow in the registry\n   */\n  async addWorkflow(name: string, workflow: Workflow): Promise<void> {\n    this.workflows.set(name, workflow);\n  }\n} ",
    "filename": "core/components/registry.ts"
  },
  {
    "content": "import path from 'path';\nimport { fileURLToPath } from 'url';\nimport { writeFile } from '../../utils/fs.js';\nimport { ComponentRegistry, ComponentType, PathGenerator } from './index.js';\nimport logger from '../../utils/logger.js';\n\n// Get the directory of this file to use for relative paths\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst basePath = path.resolve(__dirname, '../../../');\n\n// Create example task, set, phase, and workflow files for testing\nasync function createExampleFiles() {\n  // Create directory structure\n  const examplesDir = path.join(basePath, 'examples');\n  const workflowDir = path.join(examplesDir, 'simple');\n  const templatesDir = path.join(workflowDir, 'templates');\n  const tasksDir = path.join(templatesDir, 'tasks');\n  const setsDir = path.join(templatesDir, 'sets');\n  const phasesDir = path.join(templatesDir, 'phases');\n  \n  // Create task template\n  const taskContent = `---\nname: \"example-task\"\nrole: \"service\"\nrequiredInput: [\"input1\", \"input2\"]\nrequiredOutput: [\"output1\"]\n---\n\n# Example Task Template\n\nThis is an example task template for testing the component registry.\n\n## Task Instructions\nPerform the following steps:\n1. Process {{input1}}\n2. Consider {{input2}}\n3. Generate {{output1}}\n`;\n\n  // Create set template\n  const setContent = `name: \"example-set\"\ndescription: \"Example set for testing\"\nexecution: \"sequential\"\ntasks:\n  - description: \"First task\"\n    useTask: \"example-task\"\n    variables:\n      input1: \"value1\"\n      input2: \"value2\"\n  \n  - description: \"Second task\"\n    useTask: \"example-task\"\n    variables:\n      input1: \"value3\"\n      input2: \"value4\"\nrequiredInput: [\"setInput\"]\nrequiredOutput: [\"setOutput\"]\n`;\n\n  // Create phase template\n  const phaseContent = `name: \"example-phase\"\ndescription: \"Example phase for testing\"\nexecution: \"sequential\"\nhumanInputRequired: []\nset:\n  - description: \"Example set\"\n    useSet: \"example-set\"\n    variables:\n      setInput: \"phaseValue\"\nrequiredInput: [\"phaseInput\"]\nrequiredOutput: [\"phaseOutput\"]\n`;\n\n  // Create workflow template\n  const workflowContent = `name: \"simple-workflow\"\ndescription: \"Simple workflow for component registry\"\noutputPath: \"examples-output/simple\"\nphases:\n  - usePhase: \"example-phase\"\n`;\n\n  // Create workflow-specific task\n  const workflowTaskContent = `---\nname: \"workflow-specific-task\"\nrole: \"architect\"\nrequiredInput: [\"workflowInput\"]\nrequiredOutput: [\"workflowOutput\"]\n---\n\n# Workflow-Specific Task\n\nThis task is specific to the simple workflow.\n\n## Instructions\nProcess {{workflowInput}} to generate {{workflowOutput}}.\n`;\n\n  // Write files\n  try {\n    // Write all templates and workflow files\n    await writeFile(path.join(tasksDir, 'example-task.task.md'), taskContent);\n    await writeFile(path.join(tasksDir, 'workflow-specific-task.task.md'), workflowTaskContent);\n    await writeFile(path.join(setsDir, 'example-set.set.yaml'), setContent);\n    await writeFile(path.join(phasesDir, 'example-phase.phase.yaml'), phaseContent);\n    await writeFile(path.join(workflowDir, 'workflow.yaml'), workflowContent);\n    \n    logger.info('Example files created successfully');\n  } catch (error) {\n    logger.error('Error creating example files', { error });\n    throw error;\n  }\n}\n\n// Test the component registry\nasync function testComponentRegistry() {\n  try {\n    logger.info('Testing component registry...');\n    \n    // Create a component registry\n    const registry = new ComponentRegistry(basePath);\n    \n    // Set workflow path for all tests\n    registry.setWorkflowPath('examples/simple');\n    \n    // Test task loading\n    const task = await registry.loadTask('example-task');\n    logger.info('Loaded task:', { taskName: task.task.name });\n    \n    // Test workflow-specific task loading\n    const workflowTask = await registry.loadTask('workflow-specific-task');\n    logger.info('Loaded workflow-specific task:', { taskName: workflowTask.task.name });\n    \n    // Test set loading\n    const set = await registry.loadSet('example-set');\n    logger.info('Loaded set:', { setName: set.name });\n    \n    // Test phase loading\n    const phase = await registry.loadPhase('example-phase');\n    logger.info('Loaded phase:', { phaseName: phase.name });\n    \n    // Test workflow loading\n    const workflow = await registry.loadWorkflow('examples/simple');\n    logger.info('Loaded workflow:', { workflowName: workflow.name });\n    \n    logger.info('Component registry testing completed successfully');\n  } catch (error) {\n    logger.error('Error in component registry test', { error });\n    throw error;\n  }\n}\n\n// Test the path generator\nfunction testPathGenerator() {\n  try {\n    logger.info('Testing path generator...');\n    \n    // Create a path generator\n    const pathGenerator = new PathGenerator('examples-output/simple');\n    \n    // Test task output path\n    const taskPath = pathGenerator.generateTaskOutputPath(\n      'phase1',\n      'iteration1',\n      'set1',\n      'service1',\n      'task1',\n      'thinking'\n    );\n    logger.info('Task path:', { path: taskPath });\n    \n    // Test set output path\n    const setPath = pathGenerator.generateSetOutputPath(\n      'phase1',\n      'iteration1',\n      'set1',\n      'service1'\n    );\n    logger.info('Set path:', { path: setPath });\n    \n    // Test phase output path\n    const phasePath = pathGenerator.generatePhaseOutputPath(\n      'phase1',\n      'iteration1'\n    );\n    logger.info('Phase path:', { path: phasePath });\n    \n    // Test iterated task output path generation\n    const iteratedTaskPath = pathGenerator.generateIteratedTaskOutputPath('phase1', 'set1', 'item1', 'task1');\n    logger.info('Iterated task output path:', { path: iteratedTaskPath });\n    \n    // Test iterated set output path generation\n    const iteratedSetPath = pathGenerator.generateIteratedSetOutputPath('phase1', 'set1', 'item1');\n    logger.info('Iterated set output path:', { path: iteratedSetPath });\n    \n    logger.info('Path generator testing completed successfully');\n  } catch (error) {\n    logger.error('Error in path generator test', { error });\n    throw error;\n  }\n}\n\n// Main function to run all tests\nasync function main() {\n  try {\n    logger.info('Starting component tests...');\n    \n    // Create example files\n    await createExampleFiles();\n    \n    // Test component registry\n    await testComponentRegistry();\n    \n    // Test path generator\n    testPathGenerator();\n    \n    logger.info('All component tests completed successfully');\n  } catch (error) {\n    logger.error('Error in component tests', { error });\n    process.exit(1);\n  }\n}\n\n// Run the tests\nmain(); ",
    "filename": "core/components/test.ts"
  },
  {
    "content": "/**\n * Execution module exports\n */\n\nexport * from './set-executor.js';\nexport * from './phase-executor.js';\nexport * from './workflow-executor.js'; ",
    "filename": "core/execution/index.ts"
  },
  {
    "content": "/**\n * PhaseExecutor - Executes a phase with human input management\n */\n\nimport path from 'path';\nimport { Phase, SetReference, TemplateReference } from '../components/interfaces.js';\nimport { ResumeState, addProgressEntry, isSetCompleted, setFilesAwaitingReview, ProgressEntry } from '../state/index.js';\nimport { ComponentRegistry } from '../components/registry.js';\nimport { SetExecutor } from './set-executor.js';\nimport { Context } from '../components/interfaces.js';\nimport { logger } from '../../utils/logger.js';\nimport fs from 'fs';\nimport { CommitteePaths, PathComponents } from '../../utils/paths.js';\n\n/**\n * Options for the PhaseExecutor\n */\nexport interface PhaseExecutorOptions {\n  registry: ComponentRegistry;\n  workflowPath: string;\n  outputPath: string;\n  state: ResumeState;\n  initialContext?: Context;\n  savePrompts?: boolean;\n  dryRun?: boolean;\n  apiDryRun?: boolean;\n  useHaikuModel?: boolean;\n  phaseIteration?: string;\n}\n\n/**\n * Executor for phase components\n */\nexport class PhaseExecutor {\n  private registry: ComponentRegistry;\n  private workflowPath: string;\n  private outputPath: string;\n  private state: ResumeState;\n  private initialContext: Context;\n  private savePrompts: boolean;\n  private dryRun: boolean;\n  private apiDryRun: boolean;\n  private useHaikuModel: boolean;\n  private phaseIteration?: string;\n  \n  constructor(options: PhaseExecutorOptions) {\n    this.registry = options.registry;\n    this.workflowPath = options.workflowPath;\n    this.outputPath = options.outputPath;\n    this.state = options.state;\n    this.initialContext = options.initialContext || {};\n    this.savePrompts = options.savePrompts || false;\n    this.dryRun = options.dryRun || false;\n    this.apiDryRun = options.apiDryRun || false;\n    this.useHaikuModel = options.useHaikuModel || false;\n    this.phaseIteration = options.phaseIteration;\n  }\n  \n  /**\n   * Get whether prompts should be saved\n   */\n  public shouldSavePrompts(): boolean {\n    return this.savePrompts;\n  }\n  \n  /**\n   * Get whether this is a dry run\n   */\n  public isDryRun(): boolean {\n    return this.dryRun;\n  }\n  \n  /**\n   * Get whether this is an API dry run\n   */\n  public isApiDryRun(): boolean {\n    return this.apiDryRun;\n  }\n  \n  /**\n   * Get whether to use haiku (small) models\n   */\n  public useSmallModels(): boolean {\n    return this.useHaikuModel;\n  }\n  \n  /**\n   * Get the ID of a set reference\n   */\n  private getSetId(setRef: SetReference | TemplateReference): string {\n    if ('useSet' in setRef) {\n      return setRef.useSet;\n    } else if ('template' in setRef) {\n      return setRef.template;\n    }\n    throw new Error('Invalid set reference type');\n  }\n  \n  /**\n   * Execute a set reference\n   */\n  private async executeSetReference(\n    setRef: SetReference | TemplateReference,\n    context: Context,\n    setExecutor: SetExecutor,\n    iterationName?: string\n  ): Promise<{ context: Context; state: ResumeState }> {\n    if ('useSet' in setRef) {\n      // Handle SetReference\n      const set = await this.registry.loadSet(setRef.useSet);\n      \n      // Apply variables from the set reference\n      const setContext = {\n        ...context,\n        ...(setRef.variables || {})\n      };\n      \n      // Validate required input\n      if (setRef.requiredInput) {\n        this.validateRequiredInput(setRef.requiredInput, setContext);\n      }\n      \n      // Execute the set\n      const result = await setExecutor.executeSet(set, setContext);\n      \n      // Validate required output\n      if (setRef.requiredOutput) {\n        this.validateRequiredOutput(setRef.requiredOutput, result.context);\n      }\n      \n      return result;\n    } else if ('template' in setRef) {\n      // Handle TemplateReference\n      const templateSet = await this.registry.loadSet(setRef.template);\n      \n      if (setRef.for_each) {\n        // Get the array to iterate over from the context\n        const items = context[setRef.for_each];\n        if (!Array.isArray(items)) {\n          throw new Error(`for_each value '${setRef.for_each}' is not an array in context`);\n        }\n        \n        let updatedContext = { ...context };\n        let updatedState = this.state;\n        \n        // Execute the template for each item\n        for (const item of items) {\n          // Create context for this iteration\n          const iterationContext = {\n            ...updatedContext,\n            item,\n            ...(setRef.variables || {})\n          };\n          \n          // Validate required input\n          if (setRef.requiredInput) {\n            this.validateRequiredInput(setRef.requiredInput, iterationContext);\n          }\n          \n          // Execute the template set\n          const result = await setExecutor.executeSet(templateSet, iterationContext);\n          \n          // Merge the result into the context and update the state\n          updatedContext = { ...updatedContext, ...result.context };\n          updatedState = result.state;\n          \n          // Validate required output\n          if (setRef.requiredOutput) {\n            this.validateRequiredOutput(setRef.requiredOutput, updatedContext);\n          }\n        }\n        \n        return {\n          context: updatedContext,\n          state: updatedState\n        };\n      } else {\n        // Execute the template once\n        const templateContext = {\n          ...context,\n          ...(setRef.variables || {})\n        };\n        \n        // Validate required input\n        if (setRef.requiredInput) {\n          this.validateRequiredInput(setRef.requiredInput, templateContext);\n        }\n        \n        // Execute the template set\n        const result = await setExecutor.executeSet(templateSet, templateContext);\n        \n        // Validate required output\n        if (setRef.requiredOutput) {\n          this.validateRequiredOutput(setRef.requiredOutput, result.context);\n        }\n        \n        return result;\n      }\n    }\n    \n    throw new Error('Invalid set reference type');\n  }\n  \n  /**\n   * Execute a phase\n   * @param phase The phase to execute\n   * @param context The context to use for execution\n   * @returns The updated context and state after execution\n   */\n  async executePhase(\n    phase: Phase,\n    context: Context = {}\n  ): Promise<{ context: Context; state: ResumeState }> {\n    logger.info(`Executing phase: ${phase.name}`, {\n      dryRun: this.dryRun,\n      savePrompts: this.savePrompts,\n      apiDryRun: this.apiDryRun,\n      useHaikuModel: this.useHaikuModel,\n      workflowPath: this.workflowPath,\n      outputPath: this.outputPath\n    });\n    \n    // Check phase condition first\n    if (phase.condition) {\n      const conditionResult = await this.evaluateCondition(phase.condition, context);\n      if (!conditionResult) {\n        logger.info(`Skipping phase ${phase.name} due to condition: ${phase.condition}`);\n        return { context, state: this.state };\n      }\n    }\n\n    // Merge initial context with state context\n    const mergedContext = {\n      ...(this.state.context || {}),\n      ...context\n    };\n\n    logger.debug('Phase context', {\n      phaseName: phase.name,\n      contextKeys: Object.keys(mergedContext),\n      stateContextKeys: Object.keys(this.state.context || {})\n    });\n    \n    // Validate that all required input is available\n    if (phase.requiredInput) {\n      this.validateRequiredInput(phase.requiredInput, mergedContext);\n    }\n    \n    // Create a new set executor with the appropriate path components\n    const setExecutor = new SetExecutor({\n      registry: this.registry,\n      workflowPath: this.workflowPath,\n      outputPath: this.outputPath,\n      state: this.state,\n      phaseName: phase.name,\n      savePrompts: this.savePrompts || this.dryRun, // Always save prompts in dry run mode\n      dryRun: this.dryRun,\n      apiDryRun: this.apiDryRun,\n      useHaikuModel: this.useHaikuModel\n    });\n\n    logger.info(`Created set executor for phase ${phase.name}`, {\n      phaseName: phase.name,\n      savePrompts: this.savePrompts || this.dryRun,\n      dryRun: this.dryRun\n    });\n\n    let updatedContext = mergedContext;\n    let updatedState = this.state;\n\n    // Handle parallel vs sequential execution\n    if (phase.execution === 'parallel') {\n      logger.info(`Executing sets in parallel for phase ${phase.name}`);\n      // Execute all sets in parallel\n      const setPromises = phase.set.map(async (setRef, index) => {\n        const setId = this.getSetId(setRef);\n        logger.info(`Starting parallel set execution: ${setId}`, {\n          index,\n          totalSets: phase.set.length,\n          dryRun: this.dryRun\n        });\n        return this.executeSetReference(setRef, updatedContext, setExecutor);\n      });\n\n      // Wait for all sets to complete\n      const results = await Promise.all(setPromises);\n\n      // Merge all results\n      results.forEach(result => {\n        updatedContext = { ...updatedContext, ...result.context };\n        updatedState = result.state;\n      });\n    } else {\n      // Sequential execution (default)\n      logger.info(`Executing sets sequentially for phase ${phase.name}`);\n      for (let i = 0; i < phase.set.length; i++) {\n        const setRef = phase.set[i];\n        const setId = this.getSetId(setRef);\n        logger.info(`Starting sequential set execution: ${setId}`, {\n          setNumber: i + 1,\n          totalSets: phase.set.length,\n          dryRun: this.dryRun\n        });\n\n        const result = await this.executeSetReference(setRef, updatedContext, setExecutor);\n        updatedContext = { ...updatedContext, ...result.context };\n        updatedState = result.state;\n\n        logger.info(`Completed set execution: ${setId}`, {\n          setNumber: i + 1,\n          totalSets: phase.set.length,\n          contextKeys: Object.keys(result.context)\n        });\n      }\n    }\n\n    // Validate that all required output is present\n    if (phase.requiredOutput) {\n      this.validateRequiredOutput(phase.requiredOutput, updatedContext);\n    }\n\n    logger.info(`Completed phase execution: ${phase.name}`, {\n      contextKeys: Object.keys(updatedContext),\n      dryRun: this.dryRun\n    });\n\n    return {\n      context: updatedContext,\n      state: updatedState\n    };\n  }\n  \n  /**\n   * Check if a phase requires human input and set up the state accordingly\n   * @param phase The phase to check\n   * @param state The current resume state\n   * @returns The updated state with human input requirements\n   */\n  checkHumanInputRequirements(phase: Phase, state: ResumeState): ResumeState {\n    logger.info(`Checking human input requirements for phase: ${phase.name}`);\n    logger.info(`Phase input requirements: ${JSON.stringify(phase.humanInputRequired)}`);\n    logger.info(`Current state: ${JSON.stringify(state)}`);\n    \n    if (!phase.humanInputRequired || phase.humanInputRequired.length === 0) {\n      logger.info('No human input required for this phase');\n      return state;\n    }\n    \n    // Resolve file paths to absolute paths\n    const absolutePaths = phase.humanInputRequired.map(\n      filePath => {\n        const resolved = path.isAbsolute(filePath)\n          ? filePath\n          : path.resolve(this.workflowPath, filePath);\n        logger.info(`Resolving path: ${filePath} -> ${resolved}`);\n        return resolved;\n      }\n    );\n    \n    logger.info(`Resolved absolute paths: ${JSON.stringify(absolutePaths)}`);\n    \n    // Update the state with files awaiting review\n    const updatedState = setFilesAwaitingReview(state, absolutePaths);\n    logger.info(`Updated state: ${JSON.stringify(updatedState)}`);\n    \n    return updatedState;\n  }\n  \n  /**\n   * Validate that all required input variables are present in the context\n   * @param requiredInput The list of required input variables\n   * @param context The context to validate\n   * @throws Error if any required input is missing\n   */\n  private validateRequiredInput(requiredInput: string[], context: Context): void {\n    const missingInput = requiredInput.filter(input => !(input in context));\n    \n    if (missingInput.length > 0) {\n      throw new Error(`Missing required input variables: ${missingInput.join(', ')}`);\n    }\n  }\n  \n  /**\n   * Validate that all required output variables are present in the context\n   * @param requiredOutput The list of required output variables\n   * @param context The context to validate\n   * @throws Error if any required output is missing\n   */\n  private validateRequiredOutput(requiredOutput: string[], context: Context): void {\n    const missingOutput = requiredOutput.filter(output => !(output in context));\n    \n    if (missingOutput.length > 0) {\n      throw new Error(`Missing required output variables: ${missingOutput.join(', ')}`);\n    }\n  }\n\n  /**\n   * Evaluate a condition expression\n   */\n  private async evaluateCondition(condition: string, context: Context): Promise<boolean> {\n    // For now, implement a simple evaluation that checks if a context variable is truthy\n    // This can be expanded to support more complex conditions\n    const contextVar = condition.trim();\n    return Boolean(context[contextVar]);\n  }\n\n  /**\n   * Generate the phase-level output file\n   */\n  private async generatePhaseOutput(phase: Phase, context: Context): Promise<void> {\n    const components: PathComponents = {\n      phase: phase.name,\n      phaseIteration: this.phaseIteration,\n      set: phase.name // Using phase name as set since this is phase-level output\n    };\n\n    const outputPath = CommitteePaths.constructOutputPath(this.outputPath, components);\n    \n    // Create a summary of the phase execution\n    const summary = {\n      name: phase.name,\n      phaseIteration: this.phaseIteration,\n      timestamp: new Date().toISOString(),\n      context: context\n    };\n\n    // Ensure directory exists\n    await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });\n    \n    // Write the phase output file\n    await fs.promises.writeFile(\n      outputPath,\n      `# Phase: ${phase.name}\\n\\n${JSON.stringify(summary, null, 2)}`\n    );\n  }\n} ",
    "filename": "core/execution/phase-executor.ts"
  },
  {
    "content": "/**\n * SetExecutor - Executes a set of tasks with sequential or parallel execution\n */\n\nimport path from 'path';\nimport { Set, TaskReference, SetReference, TemplateReference, FileContext, Context, Task } from '../components/interfaces.js';\nimport { ProgressEntry, ResumeState, addProgressEntry, isSetCompleted } from '../state/index.js';\nimport { ComponentRegistry } from '../components/registry.js';\nimport { logger } from '../../utils/logger.js';\nimport fs from 'fs/promises';\nimport { CommitteePaths, PathComponents } from '../../utils/paths.js';\nimport Mustache from 'mustache';\n\n/**\n * Options for the SetExecutor\n */\nexport interface SetExecutorOptions {\n  registry: ComponentRegistry;\n  workflowPath: string;\n  outputPath: string;\n  state: ResumeState;\n  phaseName: string;\n  phaseIteration?: string;\n  savePrompts?: boolean;\n  dryRun?: boolean;\n  apiDryRun?: boolean;\n  useHaikuModel?: boolean;\n  iterationName?: string;\n  executionMode?: 'sequential' | 'parallel';\n}\n\ninterface SetExecutorInterface {\n  executeSet(\n    set: Set,\n    context: Context,\n    iterationName?: string\n  ): Promise<{ context: Context; state: ResumeState }>;\n}\n\n/**\n * Executor for set components\n */\nexport class SetExecutor implements SetExecutorInterface {\n  private registry: ComponentRegistry;\n  private workflowPath: string;\n  private outputPath: string;\n  private state: ResumeState;\n  private phaseName: string;\n  private phaseIteration?: string;\n  private savePrompts: boolean;\n  private dryRun: boolean;\n  private apiDryRun: boolean;\n  private useHaikuModel: boolean;\n  private iterationName?: string;\n  private executionMode?: 'sequential' | 'parallel';\n  \n  constructor(options: SetExecutorOptions) {\n    this.registry = options.registry;\n    this.workflowPath = options.workflowPath;\n    this.outputPath = options.outputPath;\n    this.state = options.state || { progress: [] };\n    this.phaseName = options.phaseName;\n    this.phaseIteration = options.phaseIteration;\n    this.savePrompts = options.savePrompts || false;\n    this.dryRun = options.dryRun || false;\n    this.apiDryRun = options.apiDryRun || false;\n    this.useHaikuModel = options.useHaikuModel || false;\n    this.iterationName = options.iterationName;\n    this.executionMode = options.executionMode;\n  }\n  \n  /**\n   * Get whether prompts should be saved\n   */\n  public shouldSavePrompts(): boolean {\n    return this.savePrompts;\n  }\n  \n  /**\n   * Get whether this is a dry run\n   */\n  public isDryRun(): boolean {\n    return this.dryRun;\n  }\n  \n  /**\n   * Get whether this is an API dry run\n   */\n  public isApiDryRun(): boolean {\n    return this.apiDryRun;\n  }\n  \n  /**\n   * Get whether to use haiku (small) models\n   */\n  public useSmallModels(): boolean {\n    return this.useHaikuModel;\n  }\n  \n  /**\n   * Executes a set with the given context\n   * @param set Set to execute\n   * @param context Context to use\n   * @param iterationName Optional iteration name for logging\n   * @returns Updated context and state\n   */\n  async executeSet(\n    set: Set,\n    context: Context = {},\n    iterationName?: string\n  ): Promise<{ context: Context; state: ResumeState }> {\n    logger.info(`Executing set: ${set.name}`, {\n      phaseName: this.phaseName,\n      iterationName,\n      dryRun: this.dryRun,\n      savePrompts: this.savePrompts,\n      apiDryRun: this.apiDryRun,\n      useHaikuModel: this.useHaikuModel,\n      executionMode: set.execution || 'sequential'\n    });\n    \n    // Validate required inputs\n    if (set.requiredInput) {\n      this.validateRequiredInput(set.requiredInput, context, set.name);\n    }\n\n    logger.debug('Set context', {\n      setName: set.name,\n      contextKeys: Object.keys(context),\n      taskCount: set.tasks?.length || 0\n    });\n\n    // Execute tasks based on execution mode\n    const result = set.execution === 'parallel'\n      ? await this.executeTasksInParallel(set.tasks || [], context, iterationName)\n      : await this.executeTasksSequentially(set.tasks || [], context, iterationName);\n\n    logger.debug('Set execution result', {\n      setName: set.name,\n      resultKeys: Object.keys(result),\n      dryRun: this.dryRun\n    });\n\n    // Validate required output\n    if (set.requiredOutput) {\n      this.validateRequiredOutput(set.requiredOutput, result, set.name);\n    }\n\n    logger.info(`Completed set execution: ${set.name}`, {\n      phaseName: this.phaseName,\n      iterationName,\n      dryRun: this.dryRun,\n      contextKeys: Object.keys(result)\n    });\n\n    return { context: result, state: this.state };\n  }\n  \n  /**\n   * Execute a task using the appropriate processor\n   * @param task The task to execute\n   * @param taskContext The context for the task\n   * @param fileContext The file context for the task\n   * @param iterationName The iteration name for the task\n   * @returns The result of task execution\n   */\n  private async executeTask(\n    task: Task,\n    taskContext: Context,\n    fileContext: FileContext,\n    iterationName?: string\n  ): Promise<any> {\n    logger.info(`Executing task: ${task.name}`, {\n      phaseName: this.phaseName,\n      iterationName,\n      dryRun: this.dryRun,\n      savePrompts: this.savePrompts,\n      processType: task.processType,\n      fileCollections: Object.keys(fileContext)\n    });\n    \n    const components: PathComponents = {\n      phase: this.phaseName,\n      phaseIteration: this.phaseIteration,\n      set: task.phase || this.phaseName,\n      setIteration: this.iterationName,\n      task: task.name,\n      taskIteration: iterationName\n    };\n\n    // Load task content from registry\n    const taskData = await this.registry.loadTask(task.name);\n    \n    // Create temporary files for the templates\n    const tempDir = path.join(this.workflowPath, '_temp', this.phaseName, task.name);\n    await fs.mkdir(tempDir, { recursive: true });\n    const tempTemplatePath = path.join(tempDir, `${task.name}.template.md`);\n    await fs.writeFile(tempTemplatePath, taskData.content);\n    \n    // Create output directory\n    const outputDir = path.dirname(CommitteePaths.constructPath(this.outputPath, components));\n    await fs.mkdir(outputDir, { recursive: true });\n    \n    try {\n      // Process tasks based on type\n      if (task.processType === 'two-phase') {\n        // Import the two-phase processor dynamically\n        const { runTwoPhaseProcess } = await import('../../processors/two-phase.js');\n        \n        // Configure the two-phase processor\n        const processorConfig = {\n          role: task.role || 'service',\n          phase: task.phase || this.phaseName,\n          thinkingPromptTemplate: tempTemplatePath,\n          responsePromptTemplate: tempTemplatePath,\n          context: taskContext,\n          fileContext: fileContext,\n          outputPath: {\n            thinking: {\n              prompt: this.savePrompts || this.dryRun ? CommitteePaths.constructPromptPath(this.outputPath, components, 'thinking') : undefined,\n              output: CommitteePaths.constructOutputPath(this.outputPath, components, 'thinking')\n            },\n            response: {\n              prompt: this.savePrompts || this.dryRun ? CommitteePaths.constructPromptPath(this.outputPath, components, 'response') : undefined,\n              output: CommitteePaths.constructOutputPath(this.outputPath, components)\n            }\n          },\n          modelConfig: task.modelConfig,\n          savePrompts: this.savePrompts || this.dryRun, // Always save prompts in dry run mode\n          dryRun: this.dryRun,\n          apiDryRun: this.apiDryRun,\n          useHaikuModel: this.useHaikuModel\n        } as const;\n\n        logger.debug('Two-phase processor config', {\n          taskName: task.name,\n          outputPaths: {\n            thinkingPrompt: processorConfig.outputPath.thinking.prompt,\n            thinkingOutput: processorConfig.outputPath.thinking.output,\n            responsePrompt: processorConfig.outputPath.response.prompt,\n            responseOutput: processorConfig.outputPath.response.output\n          },\n          dryRun: this.dryRun,\n          savePrompts: this.savePrompts || this.dryRun\n        });\n        \n        // Run the processor\n        const result = await runTwoPhaseProcess(processorConfig);\n        \n        // Create the task output\n        const taskOutput: Record<string, any> = {\n          thinking: result.thinking,\n          response: result.response\n        };\n        \n        // If the task has required outputs, map them directly into the task output\n        if (task.requiredOutput && task.requiredOutput.length > 0) {\n          task.requiredOutput.forEach(outputName => {\n            taskOutput[outputName] = result.response;\n          });\n        }\n        \n        // Validate required output\n        if (task.requiredOutput) {\n          this.validateRequiredOutput(task.requiredOutput, taskOutput, `task ${task.name}`);\n        }\n\n        logger.info(`Completed task execution: ${task.name}`, {\n          phaseName: this.phaseName,\n          iterationName,\n          dryRun: this.dryRun,\n          outputKeys: Object.keys(taskOutput)\n        });\n        \n        return taskOutput;\n      } else if (task.processType === 'human-review') {\n        // Import the human review processor dynamically\n        const { runHumanReview } = await import('../../processors/human-review.js');\n        \n        // Configure the human review processor\n        const processorConfig = {\n          phase: task.phase || this.phaseName,\n          promptTemplate: tempTemplatePath,\n          context: taskContext,\n          inputPath: CommitteePaths.constructPath(this.outputPath, { ...components, task: `${task.name}.i` }),\n          outputPath: CommitteePaths.constructOutputPath(this.outputPath, components, 'response'),\n          required: task.required !== false\n        };\n        \n        // Run the processor\n        const result = await runHumanReview(processorConfig);\n        \n        // For tasks with required outputs, use the response as the output variable\n        const taskOutput = task.requiredOutput?.length === 1\n          ? { [task.requiredOutput[0]]: result }\n          : { [task.name]: result };\n        \n        // Validate required output\n        if (task.requiredOutput) {\n          this.validateRequiredOutput(task.requiredOutput, taskOutput, `task ${task.name}`);\n        }\n        \n        return taskOutput;\n      } else {\n        // Default to two-phase processing for tasks without a specified process type\n        const { runTwoPhaseProcess } = await import('../../processors/two-phase.js');\n        \n        // Configure the two-phase processor\n        const processorConfig = {\n          role: task.role || 'service',\n          phase: task.phase || this.phaseName,\n          thinkingPromptTemplate: tempTemplatePath,\n          responsePromptTemplate: tempTemplatePath,\n          context: taskContext,\n          fileContext: fileContext,\n          outputPath: {\n            thinking: {\n              prompt: this.savePrompts ? CommitteePaths.constructPromptPath(this.outputPath, components, 'thinking') : undefined,\n              output: CommitteePaths.constructOutputPath(this.outputPath, components, 'thinking')\n            },\n            response: {\n              prompt: this.savePrompts ? CommitteePaths.constructPromptPath(this.outputPath, components, 'response') : undefined,\n              output: CommitteePaths.constructOutputPath(this.outputPath, components)\n            }\n          },\n          modelConfig: task.modelConfig,\n          savePrompts: this.savePrompts,\n          dryRun: this.dryRun,\n          apiDryRun: this.apiDryRun,\n          useHaikuModel: this.useHaikuModel\n        } as const;\n        \n        // Run the processor\n        const result = await runTwoPhaseProcess(processorConfig);\n        \n        // For tasks with required outputs, use the response as the output variable\n        if (task.requiredOutput?.length === 1) {\n          return {\n            thinking: result.thinking,\n            [task.requiredOutput[0]]: result.response\n          };\n        }\n        \n        // Otherwise, return the response with the task's name\n        return {\n          thinking: result.thinking,\n          [task.name]: result.response\n        };\n      }\n    } finally {\n      // Clean up temporary files\n      await fs.rm(tempDir, { recursive: true, force: true });\n    }\n  }\n  \n  /**\n   * Execute tasks sequentially\n   * @param tasks The tasks to execute\n   * @param context The context to use for execution\n   * @param iterationName The iteration name for the tasks\n   * @returns The updated context after execution\n   */\n  private async executeTasksSequentially(\n    tasks: TaskReference[],\n    context: Context,\n    iterationName?: string\n  ): Promise<Context> {\n    let updatedContext = { ...context };\n    \n    for (const taskRef of tasks) {\n      // Load the task from the registry\n      const { task, content } = await this.registry.loadTask(taskRef.useTask);\n      \n      // Apply variables from the task reference\n      const taskContext = {\n        ...updatedContext,\n        ...(taskRef.variables || {})\n      };\n      \n      // Combine file context from task and task reference\n      const fileContext: FileContext = {};\n      if (task.context) {\n        Object.assign(fileContext, task.context);\n      }\n      if (taskRef.context) {\n        Object.assign(fileContext, taskRef.context);\n      }\n      \n      // Validate required input\n      if (taskRef.requiredInput) {\n        this.validateRequiredInput(taskRef.requiredInput, taskContext, taskRef.useTask);\n      } else if (task.requiredInput) {\n        this.validateRequiredInput(task.requiredInput, taskContext, task.name);\n      }\n      \n      // Execute the task\n      const result = await this.executeTask(task, taskContext, fileContext, iterationName);\n      \n      // Merge the result into the context\n      updatedContext = {\n        ...updatedContext,\n        ...result\n      };\n      \n      // Validate required output\n      if (taskRef.requiredOutput) {\n        this.validateRequiredOutput(taskRef.requiredOutput, updatedContext, `sequential task ${taskRef.useTask}`);\n      } else if (task.requiredOutput) {\n        this.validateRequiredOutput(task.requiredOutput, updatedContext, `sequential task ${task.name}`);\n      }\n    }\n    \n    return updatedContext;\n  }\n  \n  /**\n   * Execute tasks in parallel\n   * @param tasks The tasks to execute\n   * @param context The context to use for execution\n   * @param iterationName The iteration name for the tasks\n   * @returns The updated context after execution\n   */\n  private async executeTasksInParallel(\n    tasks: TaskReference[],\n    context: Context,\n    iterationName?: string\n  ): Promise<Context> {\n    // Start with a copy of the input context\n    const baseContext = { ...context };\n    \n    // Validate required input for all tasks first\n    for (const taskRef of tasks) {\n      const { task } = await this.registry.loadTask(taskRef.useTask);\n      \n      // Apply variables to create task-specific context\n      const taskContext = {\n        ...baseContext,\n        ...(taskRef.variables || {})\n      };\n      \n      if (taskRef.requiredInput) {\n        this.validateRequiredInput(taskRef.requiredInput, taskContext, taskRef.useTask);\n      } else if (task.requiredInput) {\n        this.validateRequiredInput(task.requiredInput, taskContext, task.name);\n      }\n    }\n    \n    // Execute all tasks in parallel\n    const taskPromises = tasks.map(async (taskRef) => {\n      // Load the task\n      const { task, content } = await this.registry.loadTask(taskRef.useTask);\n      \n      // Apply variables to create task-specific context\n      const taskContext = {\n        ...baseContext,\n        ...(taskRef.variables || {})\n      };\n      \n      // Combine file context from task and task reference\n      const fileContext: FileContext = {};\n      if (task.context) {\n        Object.assign(fileContext, task.context);\n      }\n      if (taskRef.context) {\n        Object.assign(fileContext, taskRef.context);\n      }\n      \n      // Execute the task\n      const result = await this.executeTask(task, taskContext, fileContext, iterationName);\n      \n      // Validate required output\n      if (taskRef.requiredOutput) {\n        this.validateRequiredOutput(taskRef.requiredOutput, { ...taskContext, ...result }, `parallel task ${taskRef.useTask}`);\n      } else if (task.requiredOutput) {\n        this.validateRequiredOutput(task.requiredOutput, { ...taskContext, ...result }, `parallel task ${task.name}`);\n      }\n      \n      return result;\n    });\n    \n    // Wait for all tasks to complete\n    const results = await Promise.all(taskPromises);\n    \n    // Merge all results into a single context\n    return results.reduce((merged, result) => ({ ...merged, ...result }), baseContext);\n  }\n  \n  /**\n   * Execute child sets\n   */\n  private async executeChildSets(\n    sets: (SetReference | TemplateReference)[],\n    context: Context\n  ): Promise<{ context: Context; state: ResumeState }> {\n    logger.info(`Executing ${sets.length} child sets`);\n\n    // Execute sets based on execution mode\n    const result = this.executionMode === 'parallel'\n      ? await this.executeChildSetsInParallel(sets, context, this.iterationName)\n      : await this.executeChildSetsSequentially(sets, context, this.iterationName);\n\n    // Validate required output for child sets\n    if (result.context) {\n      this.validateRequiredOutput(['context'], result, 'child sets execution');\n    }\n\n    return result;\n  }\n  \n  /**\n   * Execute child sets sequentially\n   */\n  private async executeChildSetsSequentially(\n    sets: (SetReference | TemplateReference)[],\n    context: Context,\n    iterationName?: string\n  ): Promise<{ context: Context; state: ResumeState }> {\n    let updatedContext = { ...context };\n    let updatedState = { ...this.state };\n\n    for (const setRef of sets) {\n      if ('useSet' in setRef) {\n        // Handle SetReference\n        const set = await this.registry.loadSet(setRef.useSet);\n        \n        // Validate required input\n        if (setRef.requiredInput) {\n          this.validateRequiredInput(setRef.requiredInput, updatedContext, setRef.useSet);\n        }\n        \n        // Execute the set\n        const result = await this.executeSet(set, updatedContext, iterationName);\n        \n        // Merge the result into the context and update the state\n        updatedContext = { ...updatedContext, ...result.context };\n        updatedState = result.state;\n        \n        // Validate required output\n        if (setRef.requiredOutput) {\n          this.validateRequiredOutput(setRef.requiredOutput, updatedContext, `sequential set ${setRef.useSet}`);\n        }\n      } else if ('template' in setRef) {\n        // Handle TemplateReference\n        const result = await this.executeTemplateReference(setRef, updatedContext, iterationName);\n        updatedContext = { ...updatedContext, ...result.context };\n        updatedState = result.state;\n        \n        // Validate required output\n        if (setRef.requiredOutput) {\n          this.validateRequiredOutput(setRef.requiredOutput, result.context, `template ${setRef.template}`);\n        }\n      }\n    }\n    \n    return { context: updatedContext, state: updatedState };\n  }\n  \n  /**\n   * Execute child sets in parallel\n   */\n  private async executeChildSetsInParallel(\n    sets: (SetReference | TemplateReference)[],\n    context: Context,\n    iterationName?: string\n  ): Promise<{ context: Context; state: ResumeState }> {\n    // Create an array of set execution promises\n    const setPromises = sets.map(async (setRef) => {\n      if ('useSet' in setRef) {\n        // Handle SetReference\n        const set = await this.registry.loadSet(setRef.useSet);\n        \n        // Validate required input\n        if (setRef.requiredInput) {\n          this.validateRequiredInput(setRef.requiredInput, context, setRef.useSet);\n        }\n        \n        // Execute the set\n        const result = await this.executeSet(set, context, iterationName);\n        \n        // Validate required output\n        if (setRef.requiredOutput) {\n          this.validateRequiredOutput(setRef.requiredOutput, result.context, `parallel set ${setRef.useSet}`);\n        }\n        \n        return result;\n      } else if ('template' in setRef) {\n        // Handle TemplateReference\n        return this.executeTemplateReference(setRef, context, iterationName);\n      }\n    });\n    \n    // Wait for all sets to complete\n    const results = await Promise.all(setPromises);\n    \n    // Merge all results into a single context\n    const mergedContext = results.reduce(\n      (acc, result) => ({ ...acc, ...result?.context }),\n      {}\n    );\n    \n    return { context: mergedContext, state: this.state };\n  }\n  \n  /**\n   * Validate that all required input variables are present in the context\n   * @param requiredInput The list of required input variables\n   * @param context The context to validate\n   * @param componentName Optional name of the component being validated\n   * @throws Error if any required input is missing\n   */\n  private validateRequiredInput(requiredInput: string[], context: Context, componentName?: string): void {\n    const missingInput = requiredInput.filter(input => !(input in context));\n    \n    if (missingInput.length > 0) {\n      const componentContext = componentName ? ` in ${componentName}` : '';\n      throw new Error(\n        `Missing required input variables${componentContext}: ${missingInput.join(', ')}`\n      );\n    }\n  }\n  \n  /**\n   * Validate that all required output variables are present in the context\n   * @param requiredOutput The list of required output variables\n   * @param context The context to validate\n   * @param componentName Optional name of the component being validated\n   * @throws Error if any required output is missing\n   */\n  private validateRequiredOutput(requiredOutput: string[], context: Context, componentName: string): void {\n    const missingOutput = requiredOutput.filter(output => !(output in context));\n    \n    if (missingOutput.length > 0) {\n      const availableOutputs = Object.keys(context);\n      const suggestions = missingOutput.map(missing => {\n        const closestMatch = availableOutputs.reduce((closest, current) => {\n          const currentDistance = this.levenshteinDistance(missing, current);\n          const closestDistance = this.levenshteinDistance(missing, closest);\n          return currentDistance < closestDistance ? current : closest;\n        }, availableOutputs[0]);\n        \n        return `${missing} (did you mean '${closestMatch}'?)`;\n      });\n\n      throw new Error(\n        `Missing required output variables in ${componentName}: ${suggestions.join(', ')}. ` +\n        `Available outputs: ${availableOutputs.join(', ')}`\n      );\n    }\n  }\n\n  /**\n   * Calculate the Levenshtein distance between two strings\n   */\n  private levenshteinDistance(a: string, b: string): number {\n    if (a.length === 0) return b.length;\n    if (b.length === 0) return a.length;\n\n    const matrix = [];\n\n    // Initialize matrix\n    for (let i = 0; i <= b.length; i++) {\n      matrix[i] = [i];\n    }\n    for (let j = 0; j <= a.length; j++) {\n      matrix[0][j] = j;\n    }\n\n    // Fill in the rest of the matrix\n    for (let i = 1; i <= b.length; i++) {\n      for (let j = 1; j <= a.length; j++) {\n        if (b.charAt(i - 1) === a.charAt(j - 1)) {\n          matrix[i][j] = matrix[i - 1][j - 1];\n        } else {\n          matrix[i][j] = Math.min(\n            matrix[i - 1][j - 1] + 1, // substitution\n            matrix[i][j - 1] + 1,     // insertion\n            matrix[i - 1][j] + 1      // deletion\n          );\n        }\n      }\n    }\n\n    return matrix[b.length][a.length];\n  }\n\n  /**\n   * Executes a template reference\n   * @param setRef Template reference to execute\n   * @param context Context to use\n   * @param iterationName Optional iteration name for logging\n   * @returns Updated context and state\n   */\n  private async executeTemplateReference(\n    setRef: TemplateReference,\n    context: Context,\n    iterationName?: string\n  ): Promise<{ context: Context; state: ResumeState }> {\n    // Load the set template\n    const set = await this.registry.loadSet(setRef.template);\n    \n    // Create a copy of the context\n    let updatedContext = { ...context };\n    \n    // If this is a for_each template reference\n    if (setRef.for_each) {\n      const items = updatedContext[setRef.for_each];\n      if (!Array.isArray(items)) {\n        throw new Error(`for_each target '${setRef.for_each}' must be an array`);\n      }\n      \n      // Execute the set for each item\n      const results = await Promise.all(items.map(async (item) => {\n        // Create context for this iteration with index signature\n        const iterationContext: { [key: string]: any } = {\n          ...updatedContext,\n          item\n        };\n\n        // Evaluate any template variables in the variables section\n        if (setRef.variables) {\n          for (const [key, value] of Object.entries(setRef.variables)) {\n            if (typeof value === 'string') {\n              // Use Mustache to evaluate the template string with the current context\n              iterationContext[key] = Mustache.render(value, { item, ...iterationContext }, {}, ['{{', '}}']);\n            } else {\n              iterationContext[key] = value;\n            }\n          }\n        }\n        \n        // Validate required input if specified\n        if (setRef.requiredInput) {\n          this.validateRequiredInput(setRef.requiredInput, iterationContext, setRef.template);\n        }\n        \n        // Execute the set with the iteration context\n        const result = await this.executeSet(set, iterationContext, iterationName);\n        \n        // Validate required output if specified\n        if (setRef.requiredOutput) {\n          this.validateRequiredOutput(setRef.requiredOutput, result.context, setRef.template);\n        }\n        \n        return result;\n      }));\n      \n      // Merge all results into the context\n      results.forEach((result) => {\n        updatedContext = {\n          ...updatedContext,\n          ...result.context\n        };\n        this.state = result.state;\n      });\n    } else {\n      // Execute the template once with variables\n      const templateContext = {\n        ...updatedContext,\n        ...(setRef.variables || {})\n      };\n      \n      // Validate required input if specified\n      if (setRef.requiredInput) {\n        this.validateRequiredInput(setRef.requiredInput, templateContext, setRef.template);\n      }\n      \n      // Execute the set with the template context\n      const result = await this.executeSet(set, templateContext, iterationName);\n      \n      // Validate required output if specified\n      if (setRef.requiredOutput) {\n        this.validateRequiredOutput(setRef.requiredOutput, result.context, setRef.template);\n      }\n      \n      updatedContext = { ...updatedContext, ...result.context };\n      this.state = result.state;\n    }\n    \n    return { context: updatedContext, state: this.state };\n  }\n\n  /**\n   * Generate the set-level output file\n   */\n  private async generateSetOutput(set: Set, context: Context): Promise<void> {\n    const components: PathComponents = {\n      phase: this.phaseName,\n      phaseIteration: this.phaseIteration,\n      set: set.name,\n      setIteration: this.iterationName\n    };\n\n    const outputPath = CommitteePaths.constructOutputPath(this.outputPath, components, 'response');\n    \n    // Create a summary of the set execution\n    const summary = {\n      name: set.name,\n      phase: this.phaseName,\n      phaseIteration: this.phaseIteration,\n      setIteration: this.iterationName,\n      timestamp: new Date().toISOString(),\n      context: context,\n      execution: set.execution || 'sequential',\n      tasks: set.tasks?.map(task => task.useTask) || [],\n      childSets: set.set?.map(setRef => 'useSet' in setRef ? setRef.useSet : setRef.template) || []\n    };\n\n    // Ensure directory exists\n    await fs.mkdir(path.dirname(outputPath), { recursive: true });\n    \n    // Write the set output file\n    await fs.writeFile(\n      outputPath,\n      `# Set: ${set.name}\\n\\n${JSON.stringify(summary, null, 2)}`\n    );\n  }\n\n  async run(\n    context: Context = {}\n  ): Promise<{ context: Context; state: ResumeState }> {\n    logger.debug('Running set executor', {\n      workflowPath: this.workflowPath,\n      contextKeys: Object.keys(context)\n    });\n\n    // Load the workflow set\n    const workflowSet = await this.registry.loadSet(this.workflowPath);\n\n    // Execute the workflow set\n    return this.executeSet(workflowSet, context, this.phaseIteration);\n  }\n} ",
    "filename": "core/execution/set-executor.ts"
  },
  {
    "content": "/**\n * WorkflowExecutor - Orchestrates the execution of a workflow\n */\n\nimport path from 'path';\nimport { Workflow, Phase, PhaseReference, Context } from '../components/interfaces.js';\nimport { ResumeState, loadResumeState, saveResumeState, markPathsAsProcessed } from '../state/state.js';\nimport { ComponentRegistry } from '../components/registry.js';\nimport { PhaseExecutor } from './phase-executor.js';\nimport { logger } from '../../utils/logger.js';\nimport { CommitteePaths } from '../../utils/paths.js';\n\n/**\n * Options for the WorkflowExecutor\n */\nexport interface WorkflowExecutorOptions {\n  registry: ComponentRegistry;\n  workflowPath: string;\n  savePrompts?: boolean;\n  dryRun?: boolean;\n  apiDryRun?: boolean;\n  useHaikuModel?: boolean;\n  useLocalLLM?: boolean;\n  mockTaskExecution?: boolean;\n  initialContext?: Record<string, any>;\n}\n\n/**\n * Result type for phase execution\n */\ninterface PhaseExecutionResult {\n  context: Context;\n  state: ResumeState;\n}\n\n/**\n * Executor for workflow components\n */\nexport class WorkflowExecutor {\n  private registry: ComponentRegistry;\n  private workflowPath: string;\n  private savePrompts: boolean;\n  private dryRun: boolean;\n  private apiDryRun: boolean;\n  private useHaikuModel: boolean;\n  private useLocalLLM: boolean;\n  private mockTaskExecution: boolean;\n  private initialContext: Record<string, any>;\n  private state: ResumeState | null = null;\n  \n  constructor(options: WorkflowExecutorOptions) {\n    this.registry = options.registry;\n    this.workflowPath = options.workflowPath;\n    this.savePrompts = options.savePrompts ?? false;\n    this.dryRun = options.dryRun ?? false;\n    this.apiDryRun = options.apiDryRun ?? false;\n    this.useHaikuModel = options.useHaikuModel ?? false;\n    this.useLocalLLM = options.useLocalLLM ?? false;\n    this.mockTaskExecution = options.mockTaskExecution ?? false;\n    this.initialContext = options.initialContext ?? {};\n    \n    if (this.dryRun) {\n      logger.info('Running in dry run mode - no API calls will be made');\n    }\n    \n    if (this.apiDryRun) {\n      logger.info('Running in API dry run mode - using compressed prompts for tracing');\n    }\n    \n    if (this.useHaikuModel) {\n      logger.info('Using small, inexpensive models for all API calls');\n    }\n    \n    if (this.savePrompts) {\n      logger.info('Saving prompts alongside responses');\n    }\n\n    if (this.mockTaskExecution) {\n      logger.info('Running in mock task execution mode');\n    }\n\n    if (this.useLocalLLM) {\n      logger.info('Using local LLM through LM Studio');\n    }\n  }\n  \n  /**\n   * Get whether prompts should be saved\n   */\n  public shouldSavePrompts(): boolean {\n    return this.savePrompts;\n  }\n  \n  /**\n   * Get whether this is a dry run\n   */\n  public isDryRun(): boolean {\n    return this.dryRun;\n  }\n  \n  /**\n   * Get whether this is an API dry run\n   */\n  public isApiDryRun(): boolean {\n    return this.apiDryRun;\n  }\n  \n  /**\n   * Get whether to use haiku (small) models\n   */\n  public useSmallModels(): boolean {\n    return this.useHaikuModel;\n  }\n  \n  /**\n   * Execute a workflow\n   * @param workflow The workflow to execute\n   * @param resuming Whether this execution is resuming after human input\n   * @returns True if the workflow completed, false if it was interrupted for human input\n   */\n  async executeWorkflow(workflow: Workflow, resuming = false): Promise<boolean> {\n    logger.info(`Executing workflow: ${workflow.name}`, {\n      resuming,\n      workflowPath: this.workflowPath,\n      dryRun: this.dryRun,\n      savePrompts: this.savePrompts,\n      apiDryRun: this.apiDryRun,\n      useHaikuModel: this.useHaikuModel,\n      outputPath: workflow.outputPath\n    });\n\n    // Load or initialize state\n    this.state = await loadResumeState(this.workflowPath);\n    \n    logger.debug('Current state', {\n      awaitingHumanInput: this.state.awaitingHumanInput,\n      processedPaths: this.state.processedHumanInputPaths,\n      filesAwaitingReview: this.state.filesAwaitingReview\n    });\n    \n    // If awaiting human input, we can't proceed\n    if (this.state.awaitingHumanInput && !resuming) {\n      logger.info('Workflow is waiting for human input');\n      return false;\n    }\n    \n    // Add workflow variables to initial context\n    let context = {\n      ...this.initialContext,\n      ...(workflow.variables || {})\n    };\n\n    logger.debug('Initial context', {\n      contextKeys: Object.keys(context),\n      workflowVariables: workflow.variables ? Object.keys(workflow.variables) : []\n    });\n    \n    // Validate that all required input is available\n    if (workflow.requiredInput) {\n      this.validateRequiredInput(workflow.requiredInput, context);\n    }\n    \n    // Build phase dependency graph\n    const phases = await this.buildPhaseDependencyGraph(workflow.phases);\n    logger.info(`Built phase dependency graph with ${phases.length} phases`);\n    \n    // Execute each phase in sequence\n    for (let i = 0; i < phases.length; i++) {\n      const phase = phases[i];\n      const nextPhase = i < phases.length - 1 ? phases[i + 1] : null;\n      \n      logger.info(`Processing phase: ${phase.name}`, {\n        phaseNumber: i + 1,\n        totalPhases: phases.length,\n        dryRun: this.dryRun,\n        savePrompts: this.savePrompts\n      });\n\n      // Create the phase executor\n      const phaseExecutor: PhaseExecutor = new PhaseExecutor({\n        registry: this.registry,\n        workflowPath: this.workflowPath,\n        outputPath: workflow.outputPath,\n        state: this.state || await loadResumeState(this.workflowPath),\n        initialContext: context,\n        savePrompts: this.savePrompts || this.dryRun, // Always save prompts in dry run mode\n        dryRun: this.dryRun,\n        apiDryRun: this.apiDryRun,\n        useHaikuModel: this.useHaikuModel,\n        phaseIteration: nextPhase ? CommitteePaths.getIterationName(nextPhase.variables, 0, nextPhase.name) : undefined\n      });\n      \n      // Execute the phase\n      const result: PhaseExecutionResult = await phaseExecutor.executePhase(phase, context);\n      \n      // Update context and state\n      context = result.context;\n      this.state = result.state;\n      \n      // Save state after phase execution\n      if (this.state) {\n        await saveResumeState(this.workflowPath, this.state);\n      }\n\n      logger.info(`Completed phase: ${phase.name}`, {\n        phaseNumber: i + 1,\n        totalPhases: phases.length,\n        contextKeys: Object.keys(context)\n      });\n    }\n    \n    // Validate that all required output is present\n    if (workflow.requiredOutput) {\n      this.validateRequiredOutput(workflow.requiredOutput, context);\n    }\n    \n    // List any files awaiting review\n    if (this.state?.filesAwaitingReview?.length > 0) {\n      logger.info('Files awaiting review:', {\n        files: this.state.filesAwaitingReview\n      });\n      return false;\n    }\n    \n    logger.info('Workflow execution completed successfully');\n    return true;\n  }\n  \n  /**\n   * Resume a workflow that was interrupted for human input\n   * @param workflow The workflow to resume\n   * @returns True if the workflow completed, false if it was interrupted again\n   */\n  async resumeWorkflow(workflow: Workflow): Promise<boolean> {\n    // Always reload the state from disk to ensure we have the latest state\n    logger.info('Resuming workflow, reloading state from disk');\n    this.state = await loadResumeState(this.workflowPath);\n    logger.info(`Loaded state: ${JSON.stringify(this.state)}`);\n    \n    // If not awaiting human input, start from the beginning\n    if (!this.state.awaitingHumanInput) {\n      logger.info('No human input required, continuing execution');\n      return this.executeWorkflow(workflow, true);\n    }\n    \n    // Check if all files have been reviewed\n    if (this.state.filesAwaitingReview.length > 0) {\n      logger.info('Workflow is still waiting for human input');\n      \n      // List the files awaiting review\n      logger.info('Files awaiting review:');\n      this.state.filesAwaitingReview.forEach((file: string) => {\n        logger.info(`- ${file}`);\n      });\n      \n      return false;\n    }\n    \n    // Reset the awaiting human input flag\n    logger.info('All files reviewed, resetting awaiting human input flag');\n    this.state.awaitingHumanInput = false;\n    await saveResumeState(this.workflowPath, this.state);\n    \n    // Resume execution\n    return this.executeWorkflow(workflow, true);\n  }\n  \n  /**\n   * Build a dependency graph of phases and sort them in execution order\n   * @param phaseRefs The phase references from the workflow\n   * @returns A list of phases sorted in execution order\n   */\n  private async buildPhaseDependencyGraph(phaseRefs: PhaseReference[]): Promise<Phase[]> {\n    // First, load all phases and handle for_each expansions\n    const phaseMap = new Map<string, Phase>();\n    const phases: Phase[] = [];\n    \n    for (const phaseRef of phaseRefs) {\n      // Handle for_each at the phase level\n      if (phaseRef.for_each) {\n        // Get the array to iterate over from the initial context\n        const items = this.initialContext[phaseRef.for_each];\n        if (!Array.isArray(items)) {\n          throw new Error(`for_each value '${phaseRef.for_each}' is not an array in context`);\n        }\n\n        // Create a phase instance for each item\n        for (const item of items) {\n          const phase = await this.registry.loadPhase(phaseRef.usePhase);\n          \n          // Create a unique name for this phase instance\n          const instanceName = `${phase.name}_${item.name || item.id || JSON.stringify(item)}`;\n          phase.name = instanceName;\n\n          // Add item-specific context to the phase's variables\n          phase.variables = {\n            ...phase.variables,\n            item,\n            ...(phaseRef.variables || {})\n          };\n          \n          // Apply humanInputRequired from the phase reference\n          if (phaseRef.humanInputRequired) {\n            logger.info(`Setting humanInputRequired for phase ${phase.name}: ${JSON.stringify(phaseRef.humanInputRequired)}`);\n            phase.humanInputRequired = phaseRef.humanInputRequired;\n          }\n          \n          // Apply dependsOn from the phase reference\n          if (phaseRef.dependsOn) {\n            phase.dependsOn = phaseRef.dependsOn.map(dep => {\n              // If the dependency is also a for_each phase, adjust its name\n              const depRef = phaseRefs.find(ref => ref.usePhase === dep);\n              if (depRef?.for_each) {\n                return `${dep}_${item.name || item.id || JSON.stringify(item)}`;\n              }\n              return dep;\n            });\n          }\n          \n          // Apply condition from the phase reference\n          if (phaseRef.condition) {\n            phase.condition = phaseRef.condition;\n          }\n          \n          phaseMap.set(phase.name, phase);\n          phases.push(phase);\n        }\n      } else {\n        // Regular phase without for_each\n        const phase = await this.registry.loadPhase(phaseRef.usePhase);\n        \n        // Apply variables from the phase reference\n        if (phaseRef.variables) {\n          phase.variables = {\n            ...phase.variables,\n            ...phaseRef.variables\n          };\n        }\n        \n        // Apply humanInputRequired from the phase reference\n        if (phaseRef.humanInputRequired) {\n          logger.info(`Setting humanInputRequired for phase ${phase.name}: ${JSON.stringify(phaseRef.humanInputRequired)}`);\n          phase.humanInputRequired = phaseRef.humanInputRequired;\n        }\n        \n        // Apply dependsOn from the phase reference\n        if (phaseRef.dependsOn) {\n          phase.dependsOn = phaseRef.dependsOn;\n        }\n        \n        // Apply condition from the phase reference\n        if (phaseRef.condition) {\n          phase.condition = phaseRef.condition;\n        }\n        \n        phaseMap.set(phase.name, phase);\n        phases.push(phase);\n      }\n    }\n    \n    // Topological sort\n    const visited = new Set<string>();\n    const temp = new Set<string>();\n    const result: Phase[] = [];\n    \n    const visit = (phaseName: string) => {\n      if (temp.has(phaseName)) {\n        throw new Error(`Circular dependency detected in phases: ${phaseName}`);\n      }\n      \n      if (visited.has(phaseName)) {\n        return;\n      }\n      \n      temp.add(phaseName);\n      \n      const phase = phaseMap.get(phaseName);\n      if (!phase) {\n        throw new Error(`Phase not found: ${phaseName}`);\n      }\n      \n      // Visit dependencies\n      if (phase.dependsOn) {\n        for (const dep of phase.dependsOn) {\n          visit(dep);\n        }\n      }\n      \n      temp.delete(phaseName);\n      visited.add(phaseName);\n      result.push(phase);\n    };\n    \n    // Visit all phases\n    for (const phase of phases) {\n      if (!visited.has(phase.name)) {\n        visit(phase.name);\n      }\n    }\n    \n    return result;\n  }\n  \n  /**\n   * Validate that all required input variables are present in the context\n   * @param requiredInput The list of required input variables\n   * @param context The context to validate\n   * @throws Error if any required input is missing\n   */\n  private validateRequiredInput(requiredInput: string[], context: Context): void {\n    const missingInput = requiredInput.filter(input => !(input in context));\n    \n    if (missingInput.length > 0) {\n      throw new Error(`Missing required input variables: ${missingInput.join(', ')}`);\n    }\n  }\n  \n  /**\n   * Validate that all required output variables are present in the context\n   * @param requiredOutput The list of required output variables\n   * @param context The context to validate\n   * @throws Error if any required output is missing\n   */\n  private validateRequiredOutput(requiredOutput: string[], context: Context): void {\n    const missingOutput = requiredOutput.filter(output => !(output in context));\n    \n    if (missingOutput.length > 0) {\n      throw new Error(`Missing required output variables: ${missingOutput.join(', ')}`);\n    }\n  }\n} ",
    "filename": "core/execution/workflow-executor.ts"
  },
  {
    "content": "// Export Claude client\nexport * from './claude.js';\n\n// Export the components module\nexport * from './components/index.js';\n\n// Export the state management module\nexport * from './state/index.js';\n\n// Export the execution module\nexport * from './execution/index.js'; ",
    "filename": "core/index.ts"
  },
  {
    "content": "/**\n * Human input utilities for Committee\n * \n * This module provides utilities for managing human input at phase boundaries.\n */\n\nimport fs from 'fs/promises';\nimport path from 'path';\nimport yaml from 'js-yaml';\nimport { ResumeState, markFileAsReviewed, saveResumeState, loadResumeState } from './state.js';\nimport { logger } from '../../utils/logger.js';\n\n/**\n * Mark a file as reviewed by a human\n * @param workflowPath The path to the workflow directory\n * @param filePath The path of the file to mark as reviewed\n * @returns True if the file was successfully marked as reviewed\n */\nexport async function reviewFile(workflowPath: string, filePath: string): Promise<boolean> {\n  try {\n    // Check if the file exists\n    await fs.access(filePath);\n    \n    // Load the current state\n    const state = await loadResumeState(workflowPath);\n    \n    // Check if the file is awaiting review\n    if (!state.filesAwaitingReview.includes(filePath)) {\n      logger.warn(`File ${filePath} is not awaiting review`);\n      return false;\n    }\n    \n    // Mark the file as reviewed\n    const updatedState = markFileAsReviewed(state, filePath);\n    \n    // Save the updated state\n    await saveResumeState(workflowPath, updatedState);\n    \n    logger.info(`File ${filePath} marked as reviewed`);\n    \n    // Print the remaining files awaiting review\n    if (updatedState.filesAwaitingReview.length > 0) {\n      logger.info('Remaining files awaiting review:');\n      updatedState.filesAwaitingReview.forEach(file => {\n        logger.info(`- ${file}`);\n      });\n    } else {\n      logger.info('All files have been reviewed, workflow can resume');\n    }\n    \n    return true;\n  } catch (error) {\n    logger.error(`Failed to mark file as reviewed: ${error}`);\n    return false;\n  }\n}\n\n/**\n * List all files awaiting review\n * @param workflowPath The path to the workflow directory\n * @returns A list of files awaiting review\n */\nexport async function listFilesAwaitingReview(workflowPath: string): Promise<string[]> {\n  try {\n    // Load the current state\n    const state = await loadResumeState(workflowPath);\n    \n    return state.filesAwaitingReview;\n  } catch (error) {\n    logger.error(`Failed to list files awaiting review: ${error}`);\n    return [];\n  }\n}\n\n/**\n * Check if a workflow is waiting for human input\n * @param workflowPath The path to the workflow directory\n * @returns True if the workflow is waiting for human input\n */\nexport async function isAwaitingHumanInput(workflowPath: string): Promise<boolean> {\n  try {\n    // Load the current state\n    const state = await loadResumeState(workflowPath);\n    \n    return state.awaitingHumanInput;\n  } catch (error) {\n    logger.error(`Failed to check if workflow is awaiting human input: ${error}`);\n    return false;\n  }\n} ",
    "filename": "core/state/human-input.ts"
  },
  {
    "content": "/**\n * State management exports\n */\n\nexport * from './state.js';\nexport * from './human-input.js'; ",
    "filename": "core/state/index.ts"
  },
  {
    "content": "/**\n * State management for Committee\n * \n * This module implements append-only state management for tracking workflow progress\n * and managing human input requirements.\n */\n\nimport fs from 'fs/promises';\nimport path from 'path';\nimport yaml from 'js-yaml';\nimport { logger } from '../../utils/logger.js';\n\n/**\n * Entry in the progress tracker\n */\nexport interface ProgressEntry {\n  phaseId: string;\n  setId: string;\n  timestamp?: number;\n  context?: Record<string, any>;\n}\n\n/**\n * The resume state interface\n */\nexport interface ResumeState {\n  awaitingHumanInput: boolean;\n  filesAwaitingReview: string[];\n  progress: ProgressEntry[];\n  processedHumanInputPaths: string[];\n  context?: Record<string, any>;\n}\n\n/**\n * Create a new empty resume state\n */\nexport function createEmptyResumeState(): ResumeState {\n  return {\n    awaitingHumanInput: false,\n    filesAwaitingReview: [],\n    progress: [],\n    processedHumanInputPaths: [],\n    context: {}\n  };\n}\n\n/**\n * Load the resume state from a file\n * @param workflowPath The path to the workflow directory\n * @returns The loaded resume state or a new empty state if the file doesn't exist\n */\nexport async function loadResumeState(workflowPath: string): Promise<ResumeState> {\n  const statePath = path.join(workflowPath, 'resume.yaml');\n  \n  try {\n    const stateContent = await fs.readFile(statePath, 'utf-8');\n    const loadedState = yaml.load(stateContent) as Partial<ResumeState>;\n    \n    // Ensure all required fields exist - handle backward compatibility\n    return {\n      awaitingHumanInput: loadedState.awaitingHumanInput ?? false,\n      filesAwaitingReview: loadedState.filesAwaitingReview ?? [],\n      progress: loadedState.progress ?? [],\n      processedHumanInputPaths: loadedState.processedHumanInputPaths ?? [],\n      context: loadedState.context ?? {}\n    };\n  } catch (error) {\n    logger.info('No resume state found, creating a new one');\n    return createEmptyResumeState();\n  }\n}\n\n/**\n * Save the resume state to a file\n * @param workflowPath The path to the workflow directory\n * @param state The resume state to save\n */\nexport async function saveResumeState(workflowPath: string, state: ResumeState): Promise<void> {\n  const statePath = path.join(workflowPath, 'resume.yaml');\n  \n  try {\n    await fs.mkdir(path.dirname(statePath), { recursive: true });\n    await fs.writeFile(statePath, yaml.dump(state), 'utf-8');\n  } catch (error) {\n    logger.error(`Failed to save resume state: ${error}`);\n    throw error;\n  }\n}\n\n/**\n * Add a new progress entry to the state\n * @param state The current resume state\n * @param phaseId The ID of the phase that was completed\n * @param setId The ID of the set that was completed\n * @param context The context at the time of completion\n * @returns A new resume state with the added progress entry\n */\nexport function addProgressEntry(\n  state: ResumeState,\n  phaseId: string,\n  setId: string,\n  context?: Record<string, any>\n): ResumeState {\n  const newProgress = [...state.progress];\n  \n  // Only add if not already in the progress list\n  if (!isSetCompleted(state, phaseId, setId)) {\n    newProgress.push({\n      phaseId,\n      setId,\n      timestamp: Date.now(),\n      context\n    });\n  }\n  \n  return {\n    ...state,\n    progress: newProgress,\n    context: context || state.context\n  };\n}\n\n/**\n * Set files awaiting human review\n * @param state The current resume state\n * @param files The list of files requiring human review\n * @returns A new resume state with updated files awaiting review\n */\nexport function setFilesAwaitingReview(\n  state: ResumeState,\n  files: string[]\n): ResumeState {\n  return {\n    ...state,\n    awaitingHumanInput: files.length > 0,\n    filesAwaitingReview: files\n  };\n}\n\n/**\n * Check if a set has been completed according to the resume state\n * @param state The resume state\n * @param phaseId The phase ID\n * @param setId The set ID\n * @returns True if the set has been completed\n */\nexport function isSetCompleted(\n  state: ResumeState,\n  phaseId: string,\n  setId: string\n): boolean {\n  return state.progress.some(\n    entry => entry.phaseId === phaseId && entry.setId === setId\n  );\n}\n\n/**\n * Mark a file as reviewed\n * @param state The current resume state\n * @param filePath The path of the file that was reviewed\n * @returns A new resume state with updated files awaiting review\n */\nexport function markFileAsReviewed(\n  state: ResumeState,\n  filePath: string\n): ResumeState {\n  const newFilesAwaitingReview = state.filesAwaitingReview.filter(\n    file => file !== filePath\n  );\n  \n  // Add the file to processedHumanInputPaths\n  const processedPaths = [...state.processedHumanInputPaths];\n  if (!processedPaths.includes(filePath)) {\n    processedPaths.push(filePath);\n  }\n  \n  return {\n    ...state,\n    awaitingHumanInput: newFilesAwaitingReview.length > 0,\n    filesAwaitingReview: newFilesAwaitingReview,\n    processedHumanInputPaths: processedPaths\n  };\n}\n\n/**\n * Mark that human input paths have been processed\n * @param state The current resume state\n * @param paths The paths that have been processed\n * @returns A new resume state with updated processed human input paths\n */\nexport function markPathsAsProcessed(\n  state: ResumeState,\n  paths: string[]\n): ResumeState {\n  const processedPaths = [...state.processedHumanInputPaths];\n  \n  // Add paths that aren't already included\n  for (const path of paths) {\n    if (!processedPaths.includes(path)) {\n      processedPaths.push(path);\n    }\n  }\n  \n  return {\n    ...state,\n    processedHumanInputPaths: processedPaths\n  };\n} ",
    "filename": "core/state/state.ts"
  },
  {
    "content": "/**\n * Test file for the execution engine\n */\n\nimport path from 'path';\nimport fs from 'fs/promises';\nimport yaml from 'js-yaml';\nimport { fileURLToPath } from 'url';\nimport { ComponentRegistry } from './components/registry.js';\nimport { Workflow, Phase, Set, Task } from './components/interfaces.js';\nimport { WorkflowExecutor } from './execution/workflow-executor.js';\nimport { createEmptyResumeState, ResumeState, saveResumeState, loadResumeState, markFileAsReviewed } from './state/index.js';\nimport { logger } from '../utils/logger.js';\n\n// Get dirname equivalent in ESM\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Create the output directory path\nconst outputDir = path.resolve(__dirname, '../../_output/test-workflow');\n\n// Create a test workflow\nconst testWorkflow: Workflow = {\n  name: 'test-workflow',\n  description: 'Test workflow for execution engine',\n  outputPath: outputDir,\n  phases: [\n    {\n      usePhase: 'phase1',\n      dependsOn: []\n    },\n    {\n      usePhase: 'phase2',\n      dependsOn: ['phase1'],\n      humanInputRequired: [path.join(outputDir, 'phase1/example-output.o.md')]\n    }\n  ]\n};\n\n// Create test phases\nconst phase1: Phase = {\n  name: 'phase1',\n  description: 'Test phase 1',\n  execution: 'sequential',\n  set: [\n    {\n      useSet: 'set1'\n    }\n  ]\n};\n\nconst phase2: Phase = {\n  name: 'phase2',\n  description: 'Test phase 2',\n  execution: 'sequential',\n  set: [\n    {\n      useSet: 'set2'\n    }\n  ]\n};\n\n// Create test sets\nconst set1: Set = {\n  name: 'set1',\n  description: 'Test set 1',\n  execution: 'sequential',\n  tasks: [\n    {\n      description: 'Test task 1',\n      useTask: 'task1'\n    }\n  ]\n};\n\nconst set2: Set = {\n  name: 'set2',\n  description: 'Test set 2',\n  execution: 'sequential',\n  tasks: [\n    {\n      description: 'Test task 2',\n      useTask: 'task2'\n    }\n  ]\n};\n\n// Create test tasks\nconst task1: Task = {\n  name: 'task1',\n  description: 'Test task 1'\n};\n\nconst task2: Task = {\n  name: 'task2',\n  description: 'Test task 2'\n};\n\ninterface ExecuteOptions {\n  savePrompts?: boolean;\n  dryRun?: boolean;\n  apiDryRun?: boolean;\n  useHaikuModel?: boolean;\n  initialContext?: Record<string, any>;\n}\n\n/**\n * Run the test\n */\nasync function runTest() {\n  try {\n    // Create a mock registry\n    const registry = new ComponentRegistry(outputDir);\n    \n    // Add test components to registry\n    await registry.addTask('task1', task1, 'Test task 1 content');\n    await registry.addTask('task2', task2, 'Test task 2 content');\n    await registry.addSet('set1', set1);\n    await registry.addSet('set2', set2);\n    await registry.addPhase('phase1', phase1);\n    await registry.addPhase('phase2', phase2);\n    await registry.addWorkflow('test-workflow', testWorkflow);\n    \n    // Create the workflow executor\n    const workflowPath = outputDir;\n    const executor = new WorkflowExecutor({\n      registry,\n      workflowPath,\n      initialContext: { testVar: 'test value' }\n    });\n    \n    // Create a test output file to simulate phase1 output\n    const phase1Dir = path.join(workflowPath, 'phase1');\n    await fs.mkdir(phase1Dir, { recursive: true });\n    \n    const exampleOutputPath = path.join(phase1Dir, 'example-output.o.md');\n    await fs.writeFile(\n      exampleOutputPath,\n      '# Example Output\\n\\nThis is a test output file.'\n    );\n    \n    logger.info(`Created test file at: ${exampleOutputPath}`);\n    \n    // Execute the workflow\n    logger.info('Starting workflow execution');\n    const completed = await executor.executeWorkflow(testWorkflow);\n    \n    if (completed) {\n      logger.info('Workflow completed successfully');\n    } else {\n      logger.info('Workflow awaiting human input');\n      \n      // List the files awaiting review\n      const state = await loadResumeState(workflowPath);\n      logger.info('Files awaiting review:');\n      state.filesAwaitingReview.forEach(file => {\n        logger.info(`- ${file}`);\n      });\n      \n      // Properly mark the file as reviewed, which will also add it to processedHumanInputPaths\n      const fileToReview = state.filesAwaitingReview[0];\n      const updatedState = markFileAsReviewed(state, fileToReview);\n      \n      logger.info(`Updated state with markFileAsReviewed: ${JSON.stringify(updatedState)}`);\n      \n      // Save the updated state\n      await saveResumeState(workflowPath, updatedState);\n      \n      // Resume the workflow\n      logger.info('Resuming workflow execution');\n      const resumeCompleted = await executor.resumeWorkflow(testWorkflow);\n      \n      if (resumeCompleted) {\n        logger.info('Workflow completed successfully after resuming');\n      } else {\n        logger.error('Workflow still awaiting human input after resuming');\n      }\n    }\n  } catch (error) {\n    logger.error(`Test failed: ${error}`);\n    console.error(error);\n  }\n}\n\n// Run the test if this file is executed directly\nif (import.meta.url.startsWith('file:')) {\n  const modulePath = fileURLToPath(import.meta.url);\n  if (process.argv[1] === modulePath) {\n    runTest().catch(error => {\n      logger.error(`Test failed: ${error}`);\n      console.error(error);\n      process.exit(1);\n    });\n  }\n}\n\nexport { runTest };\n\n/**\n * Run a workflow from the beginning\n */\nexport async function runWorkflow(workflowFolder: string, options: ExecuteOptions = {}) {\n  logger.info('Starting workflow execution', {\n    workflowFolder,\n    options,\n    cwd: process.cwd()\n  });\n\n  try {\n    // Create registry and load workflow\n    const registry = new ComponentRegistry(process.cwd());\n    logger.info('Created component registry');\n\n    // Load the workflow\n    logger.info(`Loading workflow from ${workflowFolder}`);\n    const workflow = await registry.loadWorkflow(workflowFolder);\n    \n    logger.info('Loaded workflow', {\n      name: workflow.name,\n      phases: workflow.phases.length,\n      outputPath: workflow.outputPath\n    });\n\n    // Create the workflow executor\n    const executor = new WorkflowExecutor({\n      registry,\n      workflowPath: workflowFolder,\n      savePrompts: options.savePrompts || options.dryRun, // Always save prompts in dry run mode\n      dryRun: options.dryRun,\n      apiDryRun: options.apiDryRun,\n      useHaikuModel: options.useHaikuModel,\n      initialContext: options.initialContext || {}\n    });\n\n    logger.info('Created workflow executor', {\n      savePrompts: options.savePrompts || options.dryRun,\n      dryRun: options.dryRun,\n      apiDryRun: options.apiDryRun\n    });\n\n    // Execute the workflow\n    const completed = await executor.executeWorkflow(workflow);\n    \n    if (!completed) {\n      logger.info('Workflow paused for human input');\n      // Get the state to show what files need review\n      const state = await loadResumeState(workflowFolder);\n      if (state.filesAwaitingReview.length > 0) {\n        logger.info('Files awaiting review:', {\n          files: state.filesAwaitingReview\n        });\n      }\n    } else {\n      logger.info('Workflow completed successfully');\n    }\n  } catch (error) {\n    logger.error('Error executing workflow', {\n      error: error instanceof Error ? error.message : String(error),\n      stack: error instanceof Error ? error.stack : undefined\n    });\n    throw error;\n  }\n}\n\n/**\n * Continue a workflow from where it left off\n */\nexport async function nextStep(workflowFolder: string, options: ExecuteOptions = {}) {\n  const registry = new ComponentRegistry(workflowFolder);\n  const workflow = await registry.loadWorkflow('.');\n  \n  const executor = new WorkflowExecutor({\n    registry,\n    workflowPath: workflowFolder,\n    savePrompts: options.savePrompts,\n    dryRun: options.dryRun,\n    apiDryRun: options.apiDryRun,\n    useHaikuModel: options.useHaikuModel,\n    initialContext: options.initialContext\n  });\n\n  const completed = await executor.resumeWorkflow(workflow);\n  if (!completed) {\n    logger.info('Workflow still requires human input');\n  }\n}\n\n/**\n * Review a file in a workflow\n */\nexport async function reviewFile(workflowFolder: string, filePath: string) {\n  const state = await loadResumeState(workflowFolder);\n  if (!state) {\n    throw new Error('No workflow state found');\n  }\n\n  const absolutePath = path.isAbsolute(filePath)\n    ? filePath\n    : path.resolve(workflowFolder, filePath);\n\n  const updatedState = markFileAsReviewed(state, absolutePath);\n  await saveResumeState(workflowFolder, updatedState);\n  logger.info(`Marked ${filePath} as reviewed`);\n}\n\n/**\n * Initialize a new workflow\n */\nexport async function initWorkflow(workflowFolder: string, options: ExecuteOptions = {}) {\n  const state = createEmptyResumeState();\n  await saveResumeState(workflowFolder, state);\n  logger.info(`Initialized workflow in ${workflowFolder}`);\n} ",
    "filename": "core/test-execution.ts"
  },
  {
    "content": "/**\n * Simplified test for human input handling\n */\n\nimport path from 'path';\nimport fs from 'fs/promises';\nimport { fileURLToPath } from 'url';\nimport { ComponentRegistry } from './components/registry.js';\nimport { Workflow, Phase } from './components/interfaces.js';\nimport { WorkflowExecutor } from './execution/workflow-executor.js';\nimport { loadResumeState, saveResumeState, markFileAsReviewed } from './state/index.js';\nimport { logger } from '../utils/logger.js';\n\n// Get dirname equivalent in ESM\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Create the test directory - using absolute paths to eliminate path resolution issues\nconst testDir = path.resolve(__dirname, '../../_output/human-input-test');\nconst testFile = path.resolve(testDir, 'test-file.o.md');\n\n// Create a simple workflow with just two phases\nconst workflow: Workflow = {\n  name: 'human-input-test',\n  description: 'Test for human input handling',\n  outputPath: testDir,\n  phases: [\n    {\n      usePhase: 'phase1'\n    },\n    {\n      usePhase: 'phase2',\n      dependsOn: ['phase1'],\n      // Using the exact same absolute path format everywhere\n      humanInputRequired: [testFile]\n    }\n  ]\n};\n\n// Create simple phases\nconst phase1: Phase = {\n  name: 'phase1',\n  description: 'Phase 1',\n  execution: 'sequential',\n  set: []  // Empty set for simplicity\n};\n\nconst phase2: Phase = {\n  name: 'phase2',\n  description: 'Phase 2',\n  execution: 'sequential',\n  set: []  // Empty set for simplicity\n};\n\n/**\n * Run the test\n */\nasync function runTest() {\n  try {\n    // Create test directory\n    await fs.mkdir(testDir, { recursive: true });\n    \n    // Create test file\n    await fs.writeFile(testFile, '# Test File\\n\\nThis is a test file for human input handling.');\n    logger.info(`Created test file at: ${testFile}`);\n    \n    // Delete any existing state file\n    try {\n      await fs.unlink(path.join(testDir, 'resume.yaml'));\n      logger.info('Deleted existing state file');\n    } catch (error) {\n      // Ignore if file doesn't exist\n    }\n    \n    // Create a mock registry\n    const registry = new ComponentRegistry(testDir);\n    \n    // Add test components to registry\n    await registry.addPhase('phase1', phase1);\n    await registry.addWorkflow('test-workflow', workflow);\n    \n    // Create the workflow executor\n    const executor = new WorkflowExecutor({\n      registry,\n      workflowPath: testDir,\n      initialContext: { testVar: 'test value' }\n    });\n    \n    // Execute the workflow - should stop at phase2 waiting for human input\n    logger.info('Starting workflow execution');\n    const completed = await executor.executeWorkflow(workflow);\n    \n    if (completed) {\n      throw new Error('Workflow completed without waiting for human input');\n    }\n    \n    // At this point, the workflow should be waiting for human input\n    logger.info('Workflow is now waiting for human input');\n    \n    // Check the state file\n    const state = await loadResumeState(testDir);\n    logger.info(`Current state: ${JSON.stringify(state)}`);\n    \n    if (!state.awaitingHumanInput) {\n      throw new Error('State should indicate awaiting human input');\n    }\n    \n    if (!state.filesAwaitingReview.includes(testFile)) {\n      throw new Error(`State should include ${testFile} in files awaiting review`);\n    }\n    \n    // Properly mark the file as reviewed, which should add it to processedHumanInputPaths\n    const updatedState = markFileAsReviewed(state, testFile);\n    \n    logger.info(`Updated state with markFileAsReviewed: ${JSON.stringify(updatedState)}`);\n    \n    // Save the updated state\n    await saveResumeState(testDir, updatedState);\n    logger.info('Updated state file to mark file as reviewed');\n    \n    // Verify the state was updated\n    const verifyState = await loadResumeState(testDir);\n    logger.info(`Verified state: ${JSON.stringify(verifyState)}`);\n    \n    // Resume the workflow\n    logger.info('Resuming workflow execution');\n    const resumeCompleted = await executor.resumeWorkflow(workflow);\n    \n    if (!resumeCompleted) {\n      throw new Error('Workflow did not complete after resuming');\n    }\n    \n    logger.info('Workflow completed successfully after manual human input');\n  } catch (error) {\n    logger.error(`Test failed: ${error}`);\n    console.error(error);\n  }\n}\n\n// Run the test if this file is executed directly\nif (import.meta.url.startsWith('file:')) {\n  const modulePath = fileURLToPath(import.meta.url);\n  if (process.argv[1] === modulePath) {\n    runTest().catch(error => {\n      logger.error(`Unhandled error: ${error}`);\n      console.error(error);\n      process.exit(1);\n    });\n  }\n}\n\nexport { runTest }; ",
    "filename": "core/test-human-input.ts"
  },
  {
    "content": "import path from 'path';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\nimport { spawn } from 'child_process';\nimport { runHumanReview } from './processors/human-review.ts';\nimport { ensureDir, writeFile, readFile } from './utils/fs.ts';\nimport logger from './utils/logger.ts';\n\n// Set up the directory path based on ES modules\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst ROOT_DIR = path.resolve(__dirname, '..');\n\n/**\n * Runs a command and returns its output\n * @param command Command to run\n * @param args Command arguments\n * @returns Promise resolving to the command output\n */\nfunction runCommand(command: string, args: string[]): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const proc = spawn(command, args, { \n      stdio: ['inherit', 'pipe', 'pipe'],\n      shell: true\n    });\n    \n    let stdout = '';\n    let stderr = '';\n    \n    proc.stdout.on('data', (data) => {\n      const str = data.toString();\n      stdout += str;\n      process.stdout.write(str);\n    });\n    \n    proc.stderr.on('data', (data) => {\n      const str = data.toString();\n      stderr += str;\n      process.stderr.write(str);\n    });\n    \n    proc.on('close', (code) => {\n      if (code === 0) {\n        resolve(stdout);\n      } else {\n        reject(new Error(`Command failed with exit code ${code}\\nSTDERR: ${stderr}`));\n      }\n    });\n  });\n}\n\n// Main function\nasync function main() {\n  try {\n    // Create demo directory\n    const demoDir = path.join(ROOT_DIR, '_dev/demo-review');\n    await ensureDir(demoDir);\n    \n    logger.info('=== Committee Review Workflow Demo ===');\n    logger.info('This demo will showcase the complete review workflow:');\n    logger.info('1. Create a document for review');\n    logger.info('2. Set up a human review process');\n    logger.info('3. List pending reviews');\n    logger.info('4. Show review details');\n    logger.info('5. Approve or reject the review');\n    logger.info('');\n    \n    // Create two sample documents to review\n    const sampleDocPath1 = path.join(demoDir, 'directive1-spec.md');\n    const sampleDoc1 = `# Directive1 Type Specification\n\n## Overview\n\nThis is a sample type specification for \"directive1\" that needs review.\n\n## Type Definition\n\n\\`\\`\\`typescript\ninterface Directive1 {\n  type: 'directive1';\n  name: string;\n  config: Directive1Config;\n}\n\ninterface Directive1Config {\n  option1: string;\n  option2?: number;\n}\n\\`\\`\\`\n\n## Properties\n\n| Property | Type | Required | Description |\n|----------|------|----------|-------------|\n| type | string | Yes | Must be \"directive1\" |\n| name | string | Yes | Name of the directive |\n| config | object | Yes | Configuration object |\n\n## Examples\n\n\\`\\`\\`typescript\nconst example: Directive1 = {\n  type: 'directive1',\n  name: 'example-directive',\n  config: {\n    option1: 'value1'\n  }\n};\n\\`\\`\\`\n`;\n\n    const sampleDocPath2 = path.join(demoDir, 'directive2-spec.md');\n    const sampleDoc2 = `# Directive2 Type Specification\n\n## Overview\n\nThis is a sample type specification for \"directive2\" that needs review.\n\n## Type Definition\n\n\\`\\`\\`typescript\ninterface Directive2 {\n  type: 'directive2';\n  id: number;\n  parameters: string[];\n}\n\\`\\`\\`\n\n## Properties\n\n| Property | Type | Required | Description |\n|----------|------|----------|-------------|\n| type | string | Yes | Must be \"directive2\" |\n| id | number | Yes | Unique identifier |\n| parameters | string[] | Yes | List of parameters |\n\n## Examples\n\n\\`\\`\\`typescript\nconst example: Directive2 = {\n  type: 'directive2',\n  id: 123,\n  parameters: ['param1', 'param2']\n};\n\\`\\`\\`\n`;\n    \n    await writeFile(sampleDocPath1, sampleDoc1);\n    logger.info(`Created sample document at ${sampleDocPath1}`);\n    \n    await writeFile(sampleDocPath2, sampleDoc2);\n    logger.info(`Created sample document at ${sampleDocPath2}`);\n    \n    // Output paths for reviews\n    const outputPath1 = path.join(demoDir, 'directive1-reviewed.md');\n    const outputPath2 = path.join(demoDir, 'directive2-reviewed.md');\n    \n    // Start review processes in the background\n    logger.info('Starting review processes...');\n    \n    // Start first review process\n    const review1Promise = runHumanReview({\n      phase: 'final-spec-review',\n      promptTemplate: 'templates/human-review.md',\n      context: {\n        phase: 'Final Spec Review',\n        directiveName: 'directive1',\n        finalSpec: true\n      },\n      inputPath: sampleDocPath1,\n      outputPath: outputPath1,\n      required: true\n    }).catch(error => {\n      logger.error('Review 1 failed:', error);\n      return null;\n    });\n    \n    // Start second review process\n    const review2Promise = runHumanReview({\n      phase: 'draft-spec-review',\n      promptTemplate: 'templates/human-review.md',\n      context: {\n        phase: 'Draft Spec Review',\n        directiveName: 'directive2',\n        finalSpec: false\n      },\n      inputPath: sampleDocPath2,\n      outputPath: outputPath2,\n      required: false\n    }).catch(error => {\n      logger.error('Review 2 failed:', error);\n      return null;\n    });\n    \n    // Wait for the reviews to be set up\n    await new Promise(resolve => setTimeout(resolve, 1000));\n    \n    // Demo the review tool\n    logger.info('');\n    logger.info('=== Demo: Using the Review Tool ===');\n    logger.info('');\n    \n    // List pending reviews\n    logger.info('Listing pending reviews:');\n    await runCommand('node', ['--experimental-strip-types', 'src/bin/review-tool.ts', 'list', `--dir=${demoDir}`]);\n    logger.info('');\n    \n    // Show first review\n    logger.info('Showing details of the first review:');\n    await runCommand('node', ['--experimental-strip-types', 'src/bin/review-tool.ts', 'show', '1', `--dir=${demoDir}`]);\n    logger.info('');\n    \n    // Approve the first review\n    logger.info('Approving the first review:');\n    await runCommand('node', ['--experimental-strip-types', 'src/bin/review-tool.ts', 'approve', '1', `--dir=${demoDir}`]);\n    logger.info('');\n    \n    // Reject the second review\n    logger.info('Rejecting the second review:');\n    await runCommand('node', ['--experimental-strip-types', 'src/bin/review-tool.ts', 'reject', '2', \n      'Needs more detailed property descriptions', `--dir=${demoDir}`]);\n    logger.info('');\n    \n    // Wait for the reviews to complete\n    const [review1Result, review2Result] = await Promise.allSettled([\n      review1Promise,\n      review2Promise\n    ]);\n    \n    logger.info('Review process results:');\n    \n    if (review1Result.status === 'fulfilled' && review1Result.value) {\n      logger.info('Review 1 completed successfully:');\n      logger.info(`- Approved: ${review1Result.value.approved}`);\n      logger.info(`- Notes: ${review1Result.value.notes || 'None'}`);\n      \n      // Check the output file\n      const review1Output = await readFile(outputPath1);\n      logger.info(`- Output file length: ${review1Output.length} characters`);\n    } else {\n      logger.info('Review 1 failed or was rejected');\n    }\n    \n    if (review2Result.status === 'fulfilled' && review2Result.value) {\n      logger.info('Review 2 completed successfully:');\n      logger.info(`- Approved: ${review2Result.value.approved}`);\n      logger.info(`- Notes: ${review2Result.value.notes || 'None'}`);\n    } else if (review2Result.status === 'rejected') {\n      logger.info('Review 2 failed with error:', review2Result.reason);\n    }\n    \n    logger.info('');\n    logger.info('Demo completed.');\n  } catch (error) {\n    logger.error('Demo failed:', error);\n    process.exit(1);\n  }\n}\n\n// Run the main function\nmain().catch(error => {\n  logger.error('Unhandled error:', error);\n  process.exit(1);\n}); ",
    "filename": "demo-review-workflow.ts"
  },
  {
    "content": "#!/usr/bin/env node\n\nimport { config } from 'dotenv';\nimport { setupLogger } from './utils/logger.ts';\nimport { loadConfig } from './config/loader.ts';\n\n// Export processors\nexport * from './processors/index.js';\n\n// Export templates\nexport { renderTemplate } from './templates/index.js';\n\n// Export utils\nexport * from './utils/index.js';\n\n// Export core functionality\nexport * from './core/index.js';\n\n// Export run function\nexport * from './run.ts';\n\n// Load environment variables\nconfig();\n\n// Initialize logger\nconst logger = setupLogger();\n\nasync function main() {\n  try {\n    logger.info('Committee process started');\n    \n    // Load configuration\n    const configPath = process.argv[2] || 'config.yaml';\n    logger.info(`Loading configuration from ${configPath}`);\n    const configuration = await loadConfig(configPath);\n    \n    logger.info('Configuration loaded successfully');\n    logger.info(`Project: ${configuration.project.name}`);\n    \n    // TODO: Implement process orchestration\n    \n    logger.info('Committee process completed');\n  } catch (error) {\n    logger.error('Committee process failed', { error: (error as Error).message });\n    process.exit(1);\n  }\n}\n\nmain().catch(error => {\n  console.error('Unhandled error:', error);\n  process.exit(1);\n}); ",
    "filename": "index.ts"
  },
  {
    "content": "# Human Review Processor\n\nThe Human Review Processor facilitates structured, file-based human reviews in the Committee system. It creates review instructions, manages review status, and processes review results.\n\n## Overview\n\nThe human review process works as follows:\n\n1. The processor renders a prompt for the human reviewer explaining what to review\n2. It displays the content to be reviewed (from inputPath)\n3. The process pauses execution until the human provides input\n4. The human's response (approval, modifications, notes) is saved\n5. The review results are returned for downstream phases\n\n## Usage\n\n### Running a Human Review\n\n```typescript\nimport { runHumanReview } from './processors/human-review.ts';\n\nconst result = await runHumanReview({\n  phase: 'final-spec-review',\n  promptTemplate: 'templates/human-review.md',\n  context: {\n    phase: 'Final Spec Review',\n    directiveName: 'example-directive',\n    finalSpec: true\n  },\n  inputPath: 'path/to/input/file.md',\n  outputPath: 'path/to/output/file.md',\n  required: true\n});\n\nconsole.log(`Review approved: ${result.approved}`);\nconsole.log(`Modifications: ${result.modifications || 'None'}`);\nconsole.log(`Notes: ${result.notes || 'None'}`);\n```\n\n### Configuration Options\n\nThe `runHumanReview` function accepts a `HumanReviewConfig` object with the following properties:\n\n| Property | Type | Description |\n|----------|------|-------------|\n| phase | string | Name of the phase (e.g., 'final-spec-review') |\n| promptTemplate | string | Path to the prompt template file |\n| context | Record<string, any> | Template context for rendering the prompt |\n| inputPath | string | Path to the file to be reviewed |\n| outputPath | string | Path where the reviewed file will be saved |\n| required | boolean | Whether the review must be approved to continue |\n\n### Review Results\n\nThe function returns a `HumanReviewResult` object with the following properties:\n\n| Property | Type | Description |\n|----------|------|-------------|\n| approved | boolean | Whether the review was approved |\n| modifications | string (optional) | Description of modifications made |\n| notes | string (optional) | Additional notes from the reviewer |\n\n## Review Workflow\n\nThe review workflow is designed to be file-based and asynchronous:\n\n1. The processor creates a review instructions file at `[outputDir]/REVIEW_INSTRUCTIONS.md`\n2. It creates a status file at `[outputDir]/.review_status` to track the review\n3. The process waits for the reviewer to create a `.review` file with their feedback\n4. Once the review is complete, the process continues\n\n### Review File Format\n\nThe review file should be a JSON file with the following structure:\n\n```json\n{\n  \"approved\": true,\n  \"modifications\": \"Description of modifications (optional)\",\n  \"notes\": \"Additional notes (optional)\"\n}\n```\n\n## Review Tool\n\nA command-line review tool is provided to manage reviews:\n\n```bash\n# List all pending reviews\nnode --experimental-strip-types src/bin/review-tool.ts list\n\n# Show details of a specific review\nnode --experimental-strip-types src/bin/review-tool.ts show 1\n\n# Approve a review without modifications\nnode --experimental-strip-types src/bin/review-tool.ts approve 1\n\n# Approve a review with modifications\nnode --experimental-strip-types src/bin/review-tool.ts approve-with-mods 1 \"Description of modifications\"\n\n# Reject a review\nnode --experimental-strip-types src/bin/review-tool.ts reject 1 \"Reason for rejection\"\n```\n\n## Template Context\n\nWhen creating prompt templates, you can include variables that will be replaced with actual values:\n\n- `{{phase}}`: The current phase name\n- `{{directiveName}}`: The name of the directive being reviewed\n- Any other properties provided in the context object\n\n### Conditional Sections\n\nYou can include conditional sections using Mustache syntax:\n\n```\n{{#finalSpec}}\nThis is a final specification review.\n{{/finalSpec}}\n\n{{^finalSpec}}\nThis is a draft specification review.\n{{/finalSpec}}\n```\n\n## Example Template\n\n```markdown\n# Human Review for {{phase}}\n\n## Overview\n\nYou are conducting a review of the {{directiveName}} specification for the {{phase}} phase.\n\n## What to Look For\n\nPlease carefully review the content with these considerations in mind:\n\n1. **Correctness**: Does the specification accurately represent the requirements?\n2. **Completeness**: Does it address all the necessary aspects?\n3. **Consistency**: Is it consistent with our existing systems?\n4. **Clarity**: Is the specification clear and unambiguous?\n5. **Implementability**: Can this specification be reasonably implemented?\n\n{{#finalSpec}}\nThis is a final specification. All service feedback should have been incorporated.\n{{/finalSpec}}\n\nThank you for your careful review!\n```\n\n## Utilities\n\nSeveral utility functions are provided for working with reviews:\n\n```typescript\nimport { \n  getPendingReviews, \n  getReviewResult, \n  getReviewFilePath, \n  getReviewInstructionsPath,\n  getStatusFilePath\n} from './processors/human-review-utils.ts';\n\n// Get all pending reviews in a directory\nconst pendingReviews = await getPendingReviews('_dev/cleanup');\n\n// Get the result of a specific review\nconst reviewResult = await getReviewResult('/path/to/file.review');\n\n// Get paths for review-related files\nconst reviewFilePath = getReviewFilePath('/path/to/output.md');\nconst instructionsPath = getReviewInstructionsPath('/path/to/output.md');\nconst statusFilePath = getStatusFilePath('/path/to/output.md');\n``` ",
    "filename": "processors/README-human-review.md"
  },
  {
    "content": "# Process Engines for Committee\n\nThis module implements the core process engines for the Committee project, providing structured execution of AI-assisted workflows.\n\n## Two-Phase Processor\n\nThe Two-Phase Processor is the core execution engine that implements the \"thinking then response\" pattern. It's designed to:\n\n1. Render a thinking prompt template with context\n2. Send the thinking prompt to Claude\n3. Save the thinking output to a file\n4. Enhance the context with the thinking result\n5. Render a response prompt with the enhanced context\n6. Send the response prompt to Claude\n7. Save the response output to a file\n8. Return both thinking and response results\n\n### Usage\n\n```typescript\nimport { runTwoPhaseProcess, TwoPhaseProcessorConfig } from '../processors/two-phase.js';\nimport { createContext } from '../templates/context.js';\n\n// Create context\nconst context = createContext();\ncontext.directiveName = 'embed';\ncontext.role = 'architect';\n\n// Run the two-phase process\nconst result = await runTwoPhaseProcess({\n  role: 'architect',\n  phase: 'draft-spec-creation',\n  thinkingPromptTemplate: 'templates/draft-spec-thinking.md',\n  responsePromptTemplate: 'templates/draft-spec-response.md',\n  context,\n  outputPath: {\n    thinking: '_dev/embed/draft-spec-thinking.md',\n    response: '_dev/embed/draft-spec.md',\n  },\n  modelConfig: {\n    temperature: 0.7,\n    maxTokens: 4000,\n  },\n});\n\n// Use the results\nconsole.log('Thinking result:', result.thinking);\nconsole.log('Response result:', result.response);\n```\n\n### Configuration\n\nThe `TwoPhaseProcessorConfig` interface defines the configuration for the two-phase processor:\n\n```typescript\ninterface TwoPhaseProcessorConfig {\n  role: 'service' | 'architect' | 'pm';\n  phase: string;\n  thinkingPromptTemplate: string;\n  responsePromptTemplate: string;\n  context: Record<string, any>;\n  outputPath: {\n    thinking: string;\n    response: string;\n  };\n  modelConfig?: {\n    thinkingModel?: string;\n    responseModel?: string;\n    temperature?: number;\n    maxTokens?: number;\n  };\n}\n```\n\n### Utility Functions\n\nThe module also includes utilities for working with the two-phase processor:\n\n- `resolveOutputPaths`: Resolves output paths using placeholders\n- `mergeModelConfig`: Merges model configuration with defaults\n- `createThinkingConfig`: Creates configuration for the thinking phase\n- `createResponseConfig`: Creates configuration for the response phase\n- `validateConfig`: Validates the two-phase processor configuration\n\n### Testing\n\nYou can run the two-phase processor test script to see it in action:\n\n```\nnpm run test:two-phase\n```\n\nThis creates sample templates and runs a simple two-phase process, saving the results to the `_dev/test-two-phase` directory.\n\n## Next Steps\n\nFuture process engines to be implemented:\n\n1. Feedback Collector\n2. Architect Processor\n3. Human Review Processor\n4. Service Planning Processor\n5. Cross-Team Review Processor\n6. PM Processor ",
    "filename": "processors/README.md"
  },
  {
    "content": "import { runTwoPhaseProcess } from './two-phase.ts';\n\nexport interface ArchitectProcessorConfig {\n  phase: string;\n  thinkingPromptTemplate: string;\n  responsePromptTemplate: string;\n  context: Record<string, any>;\n  outputPath: {\n    thinking: string;\n    response: string;\n  };\n  modelConfig?: {\n    thinkingModel?: string;\n    responseModel?: string;\n    temperature?: number;\n    maxTokens?: number;\n  };\n}\n\nexport interface ArchitectProcessResult {\n  thinking: string;\n  response: string;\n}\n\n/**\n * Handles synthesis and design tasks performed by the architect role\n */\nexport async function runArchitectProcess(config: ArchitectProcessorConfig): Promise<ArchitectProcessResult> {\n  // Add architect-specific context enhancements\n  const architectContext = {\n    ...config.context,\n    role: 'architect',\n    isArchitectPhase: true\n  };\n\n  // Run the two-phase process with architect role\n  const result = await runTwoPhaseProcess({\n    role: 'architect',\n    phase: config.phase,\n    thinkingPromptTemplate: config.thinkingPromptTemplate,\n    responsePromptTemplate: config.responsePromptTemplate,\n    context: architectContext,\n    outputPath: config.outputPath,\n    modelConfig: config.modelConfig\n  });\n\n  // Format the response according to architectural document standards\n  const formattedResponse = await formatArchitectResponse(result.response);\n\n  return {\n    thinking: result.thinking,\n    response: formattedResponse\n  };\n}\n\n/**\n * Formats the architect's response according to architectural document standards\n */\nasync function formatArchitectResponse(response: string): Promise<string> {\n  // Split the response into sections\n  const sections = response.split(/(?=^#+ )/m);\n\n  // Format each section\n  const formattedSections = sections.map(section => {\n    // Add metadata section if it's the first section and doesn't have one\n    if (sections.indexOf(section) === 0 && !section.includes('## Metadata')) {\n      const metadata = [\n        '## Metadata',\n        '- Type: Architectural Document',\n        `- Created: ${new Date().toISOString()}`,\n        '- Status: Draft',\n        ''\n      ].join('\\n');\n\n      return section + '\\n' + metadata;\n    }\n    return section;\n  });\n\n  // Add standard footer if not present\n  if (!response.includes('## References')) {\n    formattedSections.push([\n      '',\n      '## References',\n      '- Previous architectural decisions',\n      '- Service feedback',\n      '- System requirements',\n      ''\n    ].join('\\n'));\n  }\n\n  return formattedSections.join('');\n} ",
    "filename": "processors/architect.ts"
  },
  {
    "content": "import path from 'path';\nimport fs from 'fs/promises';\nimport { runTwoPhaseProcess } from './two-phase.ts';\nimport type { Service } from '../config/types.ts';\n\nexport interface FeedbackCollectorConfig {\n  services: Service[];\n  phase: string;\n  thinkingPromptTemplate: string;\n  responsePromptTemplate: string;\n  context: Record<string, any>;\n  outputDir: string;\n  parallelLimit?: number; // For future use with parallel processing\n}\n\nexport interface ServiceResult {\n  service: string;\n  thinking: string;\n  response: string;\n}\n\nexport interface FeedbackCollectionResult {\n  serviceResults: ServiceResult[];\n  consolidated: string;\n}\n\n/**\n * Collects feedback from multiple services sequentially\n */\nexport async function collectFeedback(config: FeedbackCollectorConfig): Promise<FeedbackCollectionResult> {\n  const serviceResults: ServiceResult[] = [];\n\n  // Process each service sequentially\n  for (const service of config.services) {\n    // Create service-specific context\n    const serviceContext = {\n      ...config.context,\n      serviceName: service.name,\n      serviceDescription: service.description,\n      serviceDocuments: service.documents || [],\n      serviceCode: service.code || []\n    };\n\n    // Create service-specific output paths\n    const serviceOutputDir = path.join(config.outputDir, service.name);\n    await fs.mkdir(serviceOutputDir, { recursive: true });\n\n    const outputPath = {\n      thinking: path.join(serviceOutputDir, 'thinking.md'),\n      response: path.join(serviceOutputDir, 'response.md')\n    };\n\n    // Run two-phase process for this service\n    const result = await runTwoPhaseProcess({\n      role: 'service',\n      phase: config.phase,\n      thinkingPromptTemplate: config.thinkingPromptTemplate,\n      responsePromptTemplate: config.responsePromptTemplate,\n      context: serviceContext,\n      outputPath\n    });\n\n    serviceResults.push({\n      service: service.name,\n      thinking: result.thinking,\n      response: result.response\n    });\n  }\n\n  // Create consolidated view\n  const consolidated = await consolidateFeedback(serviceResults, config.outputDir);\n\n  return {\n    serviceResults,\n    consolidated\n  };\n}\n\n/**\n * Consolidates feedback from all services into a single document\n */\nasync function consolidateFeedback(results: ServiceResult[], outputDir: string): Promise<string> {\n  const consolidatedContent = [\n    '# Consolidated Service Feedback\\n',\n    '## Overview\\n',\n    `Total services processed: ${results.length}\\n`,\n    '\\n## Service Responses\\n'\n  ];\n\n  for (const result of results) {\n    consolidatedContent.push(`### ${result.service}\\n`);\n    consolidatedContent.push('#### Response\\n');\n    consolidatedContent.push(result.response);\n    consolidatedContent.push('\\n---\\n');\n  }\n\n  const consolidatedPath = path.join(outputDir, 'consolidated.md');\n  await fs.writeFile(consolidatedPath, consolidatedContent.join('\\n'));\n\n  return consolidatedContent.join('\\n');\n} ",
    "filename": "processors/feedback-collector.ts"
  },
  {
    "content": "import path from 'path';\nimport { fileExists, readFile, writeFile, listFiles } from '../utils/fs.ts';\nimport logger from '../utils/logger.ts';\n\n// Path to store review status file\nexport const REVIEW_STATUS_PATH = '.review_status';\n\n/**\n * Configuration for the human review processor\n */\nexport interface HumanReviewConfig {\n  phase: string;\n  promptTemplate: string;\n  context: Record<string, any>;\n  inputPath: string;\n  outputPath: string;\n  required: boolean;\n  timeout?: number;\n}\n\n/**\n * Represents the result of a human review\n */\nexport interface HumanReviewResult {\n  approved: boolean;\n  modifications?: string;\n  notes?: string;\n}\n\n/**\n * Represents the status of a human review\n */\nexport interface ReviewStatus {\n  phase: string;\n  status: 'pending' | 'completed';\n  inputPath: string;\n  outputPath: string;\n  reviewStarted: string;\n  reviewCompleted?: string;\n  approved?: boolean;\n}\n\n/**\n * Gets the status of all pending reviews\n * @param directory Directory to search for review status files\n * @returns Array of review status objects\n */\nexport async function getPendingReviews(directory: string): Promise<ReviewStatus[]> {\n  try {\n    // Find all review status files\n    const statusFiles = await listFiles(directory, new RegExp(`${REVIEW_STATUS_PATH}$`));\n    \n    const pendingReviews: ReviewStatus[] = [];\n    \n    for (const statusFile of statusFiles) {\n      try {\n        const statusContent = await readFile(statusFile);\n        const status = JSON.parse(statusContent) as ReviewStatus;\n        \n        if (status.status === 'pending') {\n          pendingReviews.push(status);\n        }\n      } catch (error) {\n        logger.warn(`Failed to parse review status file: ${statusFile}`, { error });\n      }\n    }\n    \n    return pendingReviews;\n  } catch (error) {\n    logger.error(`Failed to get pending reviews from directory: ${directory}`, { error });\n    throw new Error(`Unable to get pending reviews from ${directory}: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Gets the result of a review from a review file\n * @param reviewFilePath Path to the review file\n * @returns The review result, or null if the file doesn't exist or is invalid\n */\nexport async function getReviewResult(reviewFilePath: string): Promise<HumanReviewResult | null> {\n  if (!(await fileExists(reviewFilePath))) {\n    return null;\n  }\n  \n  try {\n    const reviewContent = await readFile(reviewFilePath);\n    const reviewData = JSON.parse(reviewContent);\n    \n    // Validate the review data\n    if (reviewData.approved === undefined) {\n      logger.warn(`Invalid review data: 'approved' field is missing in ${reviewFilePath}`);\n      return null;\n    }\n    \n    return {\n      approved: reviewData.approved,\n      modifications: reviewData.modifications,\n      notes: reviewData.notes\n    };\n  } catch (error) {\n    logger.warn(`Failed to parse review file: ${reviewFilePath}`, { error });\n    return null;\n  }\n}\n\n/**\n * Gets review file path from an output path\n * @param outputPath Path to the output file\n * @returns Path to the review file\n */\nexport function getReviewFilePath(outputPath: string): string {\n  return `${outputPath}.review`;\n}\n\n/**\n * Gets review instructions file path from an output path\n * @param outputPath Path to the output file\n * @returns Path to the review instructions file\n */\nexport function getReviewInstructionsPath(outputPath: string): string {\n  return `${path.dirname(outputPath)}/REVIEW_INSTRUCTIONS.md`;\n}\n\n/**\n * Gets status file path from an output path\n * @param outputPath Path to the output file\n * @returns Path to the status file\n */\nexport function getStatusFilePath(outputPath: string): string {\n  return `${path.dirname(outputPath)}/${REVIEW_STATUS_PATH}`;\n}\n\n/**\n * Main human review processor function\n * @param config Configuration for the human review\n * @returns The review result\n */\nexport async function runHumanReview(config: HumanReviewConfig): Promise<HumanReviewResult> {\n  const {\n    phase,\n    promptTemplate,\n    context,\n    inputPath,\n    outputPath,\n    required,\n    timeout = 0 // 0 means no timeout\n  } = config;\n\n  // Ensure input file exists\n  if (!(await fileExists(inputPath))) {\n    throw new Error(`Input file does not exist: ${inputPath}`);\n  }\n\n  // Create review instructions\n  const instructionsPath = getReviewInstructionsPath(outputPath);\n  await writeFile(instructionsPath, promptTemplate);\n\n  // Create review status file\n  const statusPath = getStatusFilePath(outputPath);\n  const status: ReviewStatus = {\n    phase,\n    status: 'pending',\n    inputPath,\n    outputPath,\n    reviewStarted: new Date().toISOString()\n  };\n  await writeFile(statusPath, JSON.stringify(status, null, 2));\n\n  // Wait for review completion\n  const reviewFilePath = getReviewFilePath(outputPath);\n  let result: HumanReviewResult | null = null;\n  let timeoutId: NodeJS.Timeout | undefined;\n\n  try {\n    result = await new Promise<HumanReviewResult>((resolve, reject) => {\n      // Set timeout if specified\n      if (timeout > 0) {\n        timeoutId = setTimeout(() => {\n          if (!required) {\n            resolve({\n              approved: true,\n              notes: 'Auto-approved due to timeout'\n            });\n          } else {\n            reject(new Error(`Review timeout after ${timeout}ms`));\n          }\n        }, timeout);\n      }\n\n      // Poll for review file\n      const pollInterval = setInterval(async () => {\n        const currentResult = await getReviewResult(reviewFilePath);\n        if (currentResult) {\n          clearInterval(pollInterval);\n          if (timeoutId) clearTimeout(timeoutId);\n          resolve(currentResult);\n        }\n      }, 1000); // Check every second\n    });\n\n    // Update status file\n    status.status = 'completed';\n    status.reviewCompleted = new Date().toISOString();\n    status.approved = result.approved;\n    await writeFile(statusPath, JSON.stringify(status, null, 2));\n\n    return result;\n  } catch (error) {\n    logger.error(`Human review failed for phase ${phase}`, { error });\n    throw error;\n  }\n} ",
    "filename": "processors/human-review.ts"
  },
  {
    "content": "/**\n * Processors module for Committee\n */\n\n// Export two-phase processor \nexport * from './two-phase.ts';\n\n// Export utility functions\nexport * from './two-phase-utils.ts'; \n\n// Export human review processor\nexport * from './human-review.ts'; \n\n// Export feedback collector processor\nexport * from './feedback-collector.ts';\n\n// Export architect processor\nexport * from './architect.ts'; ",
    "filename": "processors/index.ts"
  },
  {
    "content": "/**\n * Test script for the --apidry and --haiku options\n * \n * This script tests the functionality of API dry run and haiku model options\n * \n * Usage: npm run test:apidry\n */\n\nimport path from 'path';\nimport fs from 'fs/promises';\nimport getClaudeClient from '../core/claude.ts';\nimport logger from '../utils/logger.ts';\nimport * as llmxml from '../utils/llmxml.ts';\n\nasync function saveFileWithDir(filePath: string, content: string): Promise<void> {\n  await fs.mkdir(path.dirname(filePath), { recursive: true });\n  await fs.writeFile(filePath, content);\n}\n\nasync function main() {\n  try {\n    logger.info('Testing --apidry and --haiku options');\n    \n    // Create test directory\n    const testDir = 'test/api-dry';\n    await fs.mkdir(testDir, { recursive: true });\n    \n    // Create a complex prompt with multiple sections and code blocks\n    const prompt = `\n# API Analysis Task\n\n## Background\nWe need to design a new API for our e-commerce system. The API should support the following operations:\n- Product listing\n- Product details\n- Shopping cart management\n- Checkout process\n- Order history\n\n## Current System\nOur current system uses REST APIs but we're considering GraphQL for the new implementation.\n\n## Code Example (Current REST API)\n\\`\\`\\`javascript\n// Get product details\napp.get('/api/products/:id', (req, res) => {\n  const productId = req.params.id;\n  const product = db.getProduct(productId);\n  res.json(product);\n});\n\n// Add to cart\napp.post('/api/cart', (req, res) => {\n  const { productId, quantity } = req.body;\n  const cart = cartService.addToCart(req.user.id, productId, quantity);\n  res.json(cart);\n});\n\\`\\`\\`\n\n## Database Schema\n\\`\\`\\`sql\nCREATE TABLE products (\n  id SERIAL PRIMARY KEY,\n  name VARCHAR(255) NOT NULL,\n  description TEXT,\n  price DECIMAL(10, 2) NOT NULL,\n  inventory INTEGER NOT NULL\n);\n\nCREATE TABLE orders (\n  id SERIAL PRIMARY KEY,\n  user_id INTEGER REFERENCES users(id),\n  total DECIMAL(10, 2) NOT NULL,\n  status VARCHAR(50) NOT NULL,\n  created_at TIMESTAMP DEFAULT NOW()\n);\n\\`\\`\\`\n\n## Variables to Consider\n- Number of users: {{user_count}}\n- Number of products: {{product_count}}\n- Average order value: {{avg_order_value}}\n- Peak requests per second: {{peak_rps}}\n\n## Request\nPlease analyze the best API approach for our needs. Consider REST vs GraphQL tradeoffs.\n`;\n    \n    // Output paths\n    const apiDryOutputPath = path.join(testDir, 'api-dry-response.o.xml');\n    const apiDryPromptPath = path.join(testDir, 'api-dry-response.sent.xml');\n    const haikuOutputPath = path.join(testDir, 'haiku-response.o.xml');\n    const haikuPromptPath = path.join(testDir, 'haiku-response.sent.xml');\n    \n    // Get Claude client\n    const claude = getClaudeClient();\n    \n    // Test 1: API Dry Run\n    logger.info('1. Testing --apidry option');\n    \n    // Clear any existing files first\n    try {\n      await fs.unlink(apiDryOutputPath);\n      await fs.unlink(apiDryPromptPath);\n    } catch (e) {\n      // Ignore errors if files don't exist\n    }\n    \n    // Call Claude with apiDryRun option\n    const apiDryResponse = await claude.respond(prompt, {\n      apiDryRun: true,\n      outputPath: apiDryOutputPath\n    });\n    \n    // Check if prompt file exists\n    const apiDryPromptExists = await fs.stat(apiDryPromptPath)\n      .then(() => true)\n      .catch(() => false);\n    \n    if (apiDryPromptExists) {\n      logger.info('✅ API dry run prompt file was created successfully');\n      \n      // Read the content to verify\n      const savedPrompt = await fs.readFile(apiDryPromptPath, 'utf-8');\n      if (savedPrompt.includes('Compressed Prompt for API Dry Run')) {\n        logger.info('✅ API dry run prompt content is correct (compressed format)');\n      } else {\n        logger.error('❌ API dry run prompt is not in compressed format');\n      }\n    } else {\n      logger.error('❌ API dry run prompt file was not created');\n    }\n    \n    // Check the response\n    if (apiDryResponse.length < 500) {\n      logger.info('✅ API dry run response is concise as expected');\n      logger.info(`Response: ${apiDryResponse}`);\n    } else {\n      logger.error('❌ API dry run response is too verbose');\n    }\n    \n    // Test 2: Haiku Model\n    logger.info('2. Testing --haiku option');\n    \n    // Clear any existing files first\n    try {\n      await fs.unlink(haikuOutputPath);\n      await fs.unlink(haikuPromptPath);\n    } catch (e) {\n      // Ignore errors if files don't exist\n    }\n    \n    // Simplify the prompt for haiku test to save tokens/cost\n    const shortPrompt = \"What is GraphQL and how does it differ from REST APIs? Keep it brief.\";\n    \n    // Call Claude with useHaikuModel option\n    try {\n      const haikuStartTime = Date.now();\n      const haikuResponse = await claude.respond(shortPrompt, {\n        useHaikuModel: true,\n        savePrompt: true,\n        outputPath: haikuOutputPath\n      });\n      const haikuEndTime = Date.now();\n      const haikuDuration = haikuEndTime - haikuStartTime;\n      \n      logger.info(`✅ Haiku model response received in ${haikuDuration}ms`);\n      logger.info(`First 100 chars: ${haikuResponse.substring(0, 100)}...`);\n      \n      // Check if prompt file exists\n      const haikuPromptExists = await fs.stat(haikuPromptPath)\n        .then(() => true)\n        .catch(() => false);\n      \n      if (haikuPromptExists) {\n        logger.info('✅ Haiku prompt file was created successfully');\n      } else {\n        logger.error('❌ Haiku prompt file was not created');\n      }\n    } catch (error) {\n      logger.error('❌ Error using haiku model', { error });\n    }\n    \n    // Test 3: Combine API Dry Run with Haiku Model\n    logger.info('3. Testing combined --apidry --haiku options');\n    \n    const combinedOutputPath = path.join(testDir, 'combined-response.o.xml');\n    \n    try {\n      const combinedResponse = await claude.respond(shortPrompt, {\n        apiDryRun: true,\n        useHaikuModel: true,\n        outputPath: combinedOutputPath\n      });\n      \n      logger.info('✅ Combined options worked successfully');\n      logger.info(`Response: ${combinedResponse}`);\n    } catch (error) {\n      logger.error('❌ Error with combined options', { error });\n    }\n    \n    logger.info('Tests completed');\n  } catch (error) {\n    logger.error('Test failed', { error });\n    process.exit(1);\n  }\n}\n\nmain(); ",
    "filename": "processors/test-api-dry.ts"
  },
  {
    "content": "/**\n * Test script for the --prompts and --dryrun options\n * \n * This script tests the functionality of saving prompts and dry run mode\n * \n * Usage: npm run test:options\n */\n\nimport path from 'path';\nimport fs from 'fs/promises';\nimport getClaudeClient from '../core/claude.ts';\nimport logger from '../utils/logger.ts';\nimport * as llmxml from '../utils/llmxml.ts';\n\nasync function saveFileWithDir(filePath: string, content: string): Promise<void> {\n  await fs.mkdir(path.dirname(filePath), { recursive: true });\n  await fs.writeFile(filePath, content);\n}\n\nasync function main() {\n  try {\n    logger.info('Testing --prompts and --dryrun options');\n    \n    // Create test directory\n    const testDir = 'test/cli-options';\n    await fs.mkdir(testDir, { recursive: true });\n    \n    // Create a simple prompt\n    const prompt = `\n# Test Prompt\n\nThis is a test prompt to verify the --prompts and --dryrun options.\n\n## Questions\n\n1. What is the capital of France?\n2. What is 2+2?\n`;\n    \n    // Output paths\n    const outputPath = path.join(testDir, 'test-response.o.xml');\n    const expectedPromptPath = path.join(testDir, 'test-response.sent.xml');\n    \n    // Convert prompt to XML\n    const xmlPrompt = await llmxml.toXML(prompt);\n    \n    // 1. Test saving prompts\n    logger.info('Testing --prompts option');\n    const claude = getClaudeClient();\n    \n    // Clear any existing files first\n    try {\n      await fs.unlink(outputPath);\n      await fs.unlink(expectedPromptPath);\n    } catch (e) {\n      // Ignore errors if files don't exist\n    }\n    \n    // Call Claude with savePrompt option\n    await claude.respond(prompt, {\n      savePrompt: true,\n      outputPath\n    });\n    \n    // Check if prompt file exists\n    const promptFileExists = await fs.stat(expectedPromptPath)\n      .then(() => true)\n      .catch(() => false);\n    \n    if (promptFileExists) {\n      logger.info('✅ Prompt file was created successfully');\n      \n      // Read the content to verify\n      const savedPrompt = await fs.readFile(expectedPromptPath, 'utf-8');\n      if (savedPrompt.includes('Test Prompt')) {\n        logger.info('✅ Prompt content is correct');\n      } else {\n        logger.error('❌ Prompt content is incorrect');\n      }\n    } else {\n      logger.error('❌ Prompt file was not created');\n    }\n    \n    // 2. Test dry run mode\n    logger.info('Testing --dryrun option');\n    \n    // Clear any existing files first\n    try {\n      await fs.unlink(outputPath);\n      await fs.unlink(expectedPromptPath);\n    } catch (e) {\n      // Ignore errors if files don't exist\n    }\n    \n    // Call Claude with dryRun option\n    const dryRunResponse = await claude.respond(prompt, {\n      savePrompt: true,\n      dryRun: true,\n      outputPath\n    });\n    \n    // Check if prompt file exists and response is a dry run message\n    const dryRunPromptExists = await fs.stat(expectedPromptPath)\n      .then(() => true)\n      .catch(() => false);\n    \n    if (dryRunPromptExists) {\n      logger.info('✅ Prompt file was created in dry run mode');\n    } else {\n      logger.error('❌ Prompt file was not created in dry run mode');\n    }\n    \n    if (dryRunResponse.includes('DRY RUN MODE')) {\n      logger.info('✅ Dry run response contains expected placeholder');\n    } else {\n      logger.error('❌ Dry run response does not contain expected placeholder');\n    }\n    \n    logger.info('Tests completed');\n  } catch (error) {\n    logger.error('Test failed', { error });\n    process.exit(1);\n  }\n}\n\nmain(); ",
    "filename": "processors/test-cli-options.ts"
  },
  {
    "content": "import path from 'path';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\nimport { config } from 'dotenv';\nimport { collectFeedback } from './feedback-collector.ts';\nimport { runArchitectProcess } from './architect.ts';\n\n// Load environment variables from .env file\nconfig();\n\n// Get the directory name of the current module\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst projectRoot = path.resolve(__dirname, '../..');\n\n// Verify API key is loaded\nif (!process.env.ANTHROPIC_API_KEY) {\n  console.error('Error: ANTHROPIC_API_KEY environment variable is not set.');\n  console.error('Please ensure you have a .env file in the project root with ANTHROPIC_API_KEY=your_key');\n  process.exit(1);\n}\n\nasync function testFeedbackCollection() {\n  console.log('Testing feedback collection...');\n\n  const testServices = [\n    {\n      name: 'ParserService',\n      description: 'Responsible for parsing source files into AST nodes',\n      documents: ['services/pipeline/ParserService/README.md'],\n      code: ['services/pipeline/ParserService/index.ts']\n    },\n    {\n      name: 'CompilerService',\n      description: 'Compiles AST nodes into executable code',\n      documents: ['services/pipeline/CompilerService/README.md'],\n      code: ['services/pipeline/CompilerService/index.ts']\n    }\n  ];\n\n  const testContext = {\n    directiveName: 'embed',\n    phase: 'requirements',\n    baseContext: {\n      projectName: 'Committee',\n      currentPhase: 'requirements-gathering'\n    }\n  };\n\n  try {\n    const result = await collectFeedback({\n      services: testServices,\n      phase: 'requirements',\n      thinkingPromptTemplate: path.resolve(projectRoot, 'templates/requirements-thinking.md'),\n      responsePromptTemplate: path.resolve(projectRoot, 'templates/requirements-response.md'),\n      context: testContext,\n      outputDir: path.resolve(projectRoot, '_dev/test/feedback')\n    });\n\n    console.log('Feedback collection completed successfully');\n    console.log(`Number of services processed: ${result.serviceResults.length}`);\n    console.log('Consolidated feedback saved to:', path.resolve(projectRoot, '_dev/test/feedback/consolidated.md'));\n\n    // Now test architect synthesis\n    console.log('\\nTesting architect synthesis...');\n\n    const architectResult = await runArchitectProcess({\n      phase: 'requirements-synthesis',\n      thinkingPromptTemplate: path.resolve(projectRoot, 'templates/requirements-synthesis-thinking.md'),\n      responsePromptTemplate: path.resolve(projectRoot, 'templates/requirements-synthesis-response.md'),\n      context: {\n        ...testContext,\n        serviceFeedback: result.consolidated\n      },\n      outputPath: {\n        thinking: path.resolve(projectRoot, '_dev/test/architect/requirements-synthesis-thinking.md'),\n        response: path.resolve(projectRoot, '_dev/test/architect/requirements-synthesis.md')\n      }\n    });\n\n    console.log('Architect synthesis completed successfully');\n    console.log('Synthesis output saved to:', path.resolve(projectRoot, '_dev/test/architect/requirements-synthesis.md'));\n\n  } catch (error) {\n    console.error('Error during test:', error);\n  }\n}\n\n// Run the test\ntestFeedbackCollection().catch(console.error); ",
    "filename": "processors/test-feedback.ts"
  },
  {
    "content": "/**\n * Test script for the two-phase processor\n * \n * This script tests the functionality of the two-phase processor\n * without requiring the Claude API.\n * \n * Usage: node --experimental-strip-types src/processors/test-two-phase.ts\n */\n\nimport fs from 'fs/promises';\nimport path from 'path';\nimport { ensureDir } from 'fs-extra';\nimport { TemplateContext } from '../templates/context.js';\nimport { TemplateRenderer } from '../templates/renderer.js';\nimport logger from '../utils/logger.js';\nimport { resolvePath, writeFile } from '../utils/fs.js';\n\ninterface TwoPhaseConfig {\n  role: string;\n  phase: string;\n  input?: string;\n  output?: string;\n  dryRun?: boolean;\n  apiDryRun?: boolean;\n  useHaikuModel?: boolean;\n  thinkingPromptTemplate: string;\n  responsePromptTemplate: string;\n  context: TemplateContext & {\n    directiveName: string;\n    [key: string]: any;\n  };\n  outputPath: {\n    thinking: string;\n    response: string;\n  };\n}\n\n/**\n * A simplified version of the two-phase processor for testing\n */\nasync function testTwoPhaseProcess(config: TwoPhaseConfig) {\n  logger.info(`Starting test two-phase process for ${config.role} in phase ${config.phase}`);\n  \n  // Get the template directory\n  const templateDir = path.dirname(config.thinkingPromptTemplate);\n  \n  // Create a template renderer\n  const renderer = new TemplateRenderer(config.context, templateDir);\n  \n  // Resolve output paths\n  const thinking = resolvePath(config.outputPath.thinking, config.context);\n  const response = resolvePath(config.outputPath.response, config.context);\n  \n  // Create output directories\n  await ensureDir(path.dirname(thinking));\n  await ensureDir(path.dirname(response));\n  \n  // Get the template name\n  const templateName = path.basename(config.thinkingPromptTemplate);\n  \n  // Mock thinking phase\n  const thinkingResult = `# Thinking about ${config.context.directiveName}\n\nI am thinking about the ${config.context.directiveName} directive. This is a mock response.\n\n## Analysis\n\nThis is a test directive that doesn't do anything.\n\n## Considerations\n\n- Test consideration 1\n- Test consideration 2\n- Test consideration 3\n\n## Conclusion\n\nThis is a simple test directive.`;\n  \n  // Write thinking output\n  await writeFile(thinking, thinkingResult);\n  \n  // Add thinking result to context\n  const enhancedContext = { ...config.context, thinking: thinkingResult };\n  \n  // Get response template name\n  const responseTemplateName = path.basename(config.responsePromptTemplate);\n  \n  // Mock response phase\n  const responseResult = `# ${config.context.directiveName} Specification\n\n## Overview\n\nThe \\`${config.context.directiveName}\\` is a simple directive for testing purposes.\n\n## Implementation\n\nThis is a mock implementation.\n\n## Examples\n\n\\`\\`\\`yaml\ntest: value\n\\`\\`\\`\n\n## Notes\n\nThis is a test directive.`;\n  \n  // Write response output\n  await writeFile(response, responseResult);\n  \n  return {\n    thinking: thinkingResult,\n    response: responseResult\n  };\n}\n\n/**\n * Create test template files\n */\nasync function createTestTemplates() {\n  // Create test directory if it doesn't exist\n  const testDir = path.join(process.cwd(), '_dev', 'test-two-phase');\n  await fs.mkdir(testDir, { recursive: true });\n  \n  // Create thinking template\n  const thinkingTemplatePath = path.join(testDir, 'thinking-template.md');\n  const thinkingTemplate = `\n# Test Thinking Template\n\n## Context\nI am the {{role}} for the {{directiveName}} directive.\n\n## Task\nThink about what the {{directiveName}} directive should do.\n\nConsider:\n1. What properties should it have?\n2. How should it be processed?\n3. What use cases should it support?\n\n## Now think deeply about this problem.\n`;\n\n  // Create response template\n  const responseTemplatePath = path.join(testDir, 'response-template.md');\n  const responseTemplate = `\n# Test Response Template\n\n## Context\nI am the {{role}} for the {{directiveName}} directive.\n\n## Thinking\nHere's what I thought about:\n{{thinking}}\n\n## Task\nBased on my thinking above, create a draft specification for the {{directiveName}} directive.\n`;\n\n  // Write templates to disk\n  await fs.writeFile(thinkingTemplatePath, thinkingTemplate, 'utf8');\n  await fs.writeFile(responseTemplatePath, responseTemplate, 'utf8');\n  \n  return {\n    thinkingTemplatePath,\n    responseTemplatePath,\n    outputDir: testDir,\n  };\n}\n\n/**\n * Run the test\n */\nasync function runTest() {\n  try {\n    // Create test templates\n    await createTestTemplates();\n    \n    // Create context\n    const context = {\n      directiveName: 'test-directive',\n      description: 'A test directive',\n      parameters: ['param1', 'param2'],\n      examples: ['example1', 'example2']\n    };\n    \n    // Configure test\n    const config: TwoPhaseConfig = {\n      role: 'test-role',\n      phase: 'test-phase',\n      thinkingPromptTemplate: 'templates/thinking.md',\n      responsePromptTemplate: 'templates/response.md',\n      context,\n      outputPath: {\n        thinking: 'output/thinking.md',\n        response: 'output/response.md'\n      }\n    };\n    \n    // Run test\n    await testTwoPhaseProcess(config);\n    \n    logger.info('Test completed successfully');\n  } catch (error) {\n    logger.error('Test failed:', error);\n    process.exit(1);\n  }\n}\n\n// Run the test\nrunTest(); ",
    "filename": "processors/test-two-phase.ts"
  },
  {
    "content": "import { resolvePath } from '../utils/fs.ts';\nimport * as twoPhaseModule from './two-phase.ts';\nimport logger from '../utils/logger.ts';\nimport fs from 'fs/promises';\nimport Mustache from 'mustache';\nimport path from 'path';\nimport { TemplateRenderer } from '../templates/renderer.ts';\nimport { FileContext } from '../core/components/interfaces.ts';\n\n/**\n * Default model configuration for the two-phase processor\n */\nexport const defaultModelConfig = {\n  thinkingModel: process.env.THINKING_MODEL || 'claude-3-7-sonnet-latest',\n  responseModel: process.env.RESPONSE_MODEL || 'claude-3-7-sonnet-latest',\n  temperature: parseFloat(process.env.TEMPERATURE || '0.7'),\n  maxTokens: parseInt(process.env.MAX_TOKENS || '4000', 10),\n  useXML: process.env.USE_XML === 'true', // Add XML support option\n};\n\n/**\n * Creates the output paths for the two-phase processor\n * @param config Two-phase processor configuration\n * @returns Resolved output paths\n */\nexport function resolveOutputPaths(config: twoPhaseModule.TwoPhaseProcessorConfig): { \n  thinking: { prompt?: string; output: string }; \n  response: { prompt?: string; output: string }; \n} {\n  const resolvedPaths = resolvePath(config.outputPath, config.context) as {\n    thinking: { prompt?: string; output: string };\n    response: { prompt?: string; output: string };\n  };\n  \n  logger.debug('Resolved output paths', { \n    thinking: resolvedPaths.thinking,\n    response: resolvedPaths.response\n  });\n  \n  return resolvedPaths;\n}\n\n/**\n * Merges the default model configuration with the provided configuration\n * @param modelConfig Partial model configuration\n * @returns Complete model configuration\n */\nexport function mergeModelConfig(modelConfig?: Partial<typeof defaultModelConfig>): typeof defaultModelConfig {\n  if (!modelConfig) {\n    return defaultModelConfig;\n  }\n  \n  return {\n    thinkingModel: modelConfig.thinkingModel || defaultModelConfig.thinkingModel,\n    responseModel: modelConfig.responseModel || defaultModelConfig.responseModel,\n    temperature: modelConfig.temperature !== undefined ? modelConfig.temperature : defaultModelConfig.temperature,\n    maxTokens: modelConfig.maxTokens !== undefined ? modelConfig.maxTokens : defaultModelConfig.maxTokens,\n    useXML: modelConfig.useXML !== undefined ? modelConfig.useXML : defaultModelConfig.useXML,\n  };\n}\n\n/**\n * Creates a configuration for the thinking phase\n * @param config Two-phase processor configuration\n * @returns Configuration for the thinking phase\n */\nexport function createThinkingConfig(config: twoPhaseModule.TwoPhaseProcessorConfig): {\n  model: string;\n  temperature: number;\n  maxTokens: number;\n  useXML?: boolean;\n} {\n  const mergedConfig = mergeModelConfig(config.modelConfig);\n  \n  return {\n    model: mergedConfig.thinkingModel,\n    temperature: mergedConfig.temperature,\n    maxTokens: mergedConfig.maxTokens,\n    useXML: mergedConfig.useXML,\n  };\n}\n\n/**\n * Creates a configuration for the response phase\n * @param config Two-phase processor configuration\n * @returns Configuration for the response phase\n */\nexport function createResponseConfig(config: twoPhaseModule.TwoPhaseProcessorConfig): {\n  model: string;\n  temperature: number;\n  maxTokens: number;\n  useXML?: boolean;\n} {\n  const mergedConfig = mergeModelConfig(config.modelConfig);\n  \n  return {\n    model: mergedConfig.responseModel,\n    temperature: mergedConfig.temperature,\n    maxTokens: mergedConfig.maxTokens,\n    useXML: mergedConfig.useXML,\n  };\n}\n\n/**\n * Renders a template file with the given context and optional file collections\n * @param templatePath Template file path\n * @param context Context for variables\n * @param fileContext Optional file collections\n * @param useXML Whether to convert to XML format\n * @param isLLMResponse Whether this is an LLM response being reused\n * @returns Rendered template content\n */\nexport async function renderTemplate(\n  templatePath: string, \n  context: Record<string, any>,\n  fileContext?: FileContext,\n  useXML?: boolean,\n  isLLMResponse?: boolean\n): Promise<string> {\n  logger.debug(`Rendering template: ${templatePath}`, {\n    contextKeys: Object.keys(context),\n    hasFileContext: !!fileContext,\n    fileCollections: fileContext ? Object.keys(fileContext) : [],\n    useXML,\n    isLLMResponse\n  });\n  \n  // Use the enhanced template renderer for all cases to ensure consistent handling\n  const templateDir = path.dirname(templatePath);\n  const renderer = new TemplateRenderer(context, templateDir, useXML);\n  \n  // Register file collections if provided\n  if (fileContext) {\n    renderer.registerCollections(fileContext);\n  }\n  \n  // Render the template with LLM response handling\n  const templateName = path.basename(templatePath);\n  return renderer.renderTemplate(templateName, { isLLMResponse });\n} ",
    "filename": "processors/two-phase-utils.ts"
  },
  {
    "content": "import fs from 'fs/promises';\nimport path from 'path';\nimport getClaudeClient from '../core/claude.ts';\nimport { renderTemplate } from './two-phase-utils.ts';\nimport logger from '../utils/logger.ts';\nimport { FileContext } from '../core/components/interfaces.ts';\n\n/**\n * Configuration for the two-phase processor\n */\nexport interface TwoPhaseProcessorConfig {\n  role: 'service' | 'architect' | 'pm';\n  phase: string;\n  thinkingPromptTemplate: string;\n  responsePromptTemplate: string;\n  context: Record<string, any>;\n  fileContext?: FileContext;\n  outputPath: {\n    thinking: {\n      prompt?: string;\n      output: string;\n    };\n    response: {\n      prompt?: string;\n      output: string;\n    };\n  };\n  modelConfig?: {\n    thinkingModel?: string;\n    responseModel?: string;\n    temperature?: number;\n    maxTokens?: number;\n    useXML?: boolean;\n  };\n  savePrompts?: boolean;\n  dryRun?: boolean;\n  apiDryRun?: boolean;\n  useHaikuModel?: boolean;\n}\n\n/**\n * Result of the two-phase process\n */\nexport interface TwoPhaseResult {\n  thinking: string;\n  response: string;\n}\n\n/**\n * Runs a two-phase process (thinking then response) using Claude\n */\nexport async function runTwoPhaseProcess(config: TwoPhaseProcessorConfig): Promise<TwoPhaseResult> {\n  logger.info(`Starting two-phase process for ${config.role} in ${config.phase} phase`, {\n    dryRun: config.dryRun,\n    savePrompts: config.savePrompts,\n    apiDryRun: config.apiDryRun,\n    useHaikuModel: config.useHaikuModel\n  });\n\n  // Get Claude client instance\n  const claude = getClaudeClient();\n\n  // Ensure output directories exist\n  await fs.mkdir(path.dirname(config.outputPath.thinking.output), { recursive: true });\n  await fs.mkdir(path.dirname(config.outputPath.response.output), { recursive: true });\n\n  // Phase 1: Thinking\n  logger.debug('Starting thinking phase');\n  const thinkingPrompt = await renderTemplate(\n    config.thinkingPromptTemplate,\n    config.context,\n    config.fileContext,\n    config.modelConfig?.useXML,\n    false // Not an LLM response\n  );\n  \n  // Save thinking prompt if requested or in dry run mode\n  if ((config.savePrompts || config.dryRun) && config.outputPath.thinking.prompt) {\n    await fs.mkdir(path.dirname(config.outputPath.thinking.prompt), { recursive: true });\n    await fs.writeFile(config.outputPath.thinking.prompt, thinkingPrompt);\n    logger.debug('Saved thinking prompt', { path: config.outputPath.thinking.prompt });\n  }\n  \n  logger.info('Sending thinking prompt to Claude...', {\n    role: config.role,\n    phase: config.phase,\n    contextKeys: Object.keys(config.context),\n    outputPath: config.outputPath.thinking.output,\n    dryRun: config.dryRun\n  });\n  \n  const thinking = await claude.think(thinkingPrompt, {\n    model: config.modelConfig?.thinkingModel,\n    temperature: config.modelConfig?.temperature,\n    maxTokens: config.modelConfig?.maxTokens,\n    useXML: config.modelConfig?.useXML,\n    savePrompt: config.savePrompts || config.dryRun,\n    dryRun: config.dryRun,\n    apiDryRun: config.apiDryRun,\n    useHaikuModel: config.useHaikuModel,\n    outputPath: config.outputPath.thinking.output\n  });\n\n  logger.info('Received thinking response from Claude', {\n    responseLength: thinking?.length || 0,\n    firstChars: thinking?.substring(0, 100),\n    dryRun: config.dryRun\n  });\n  \n  logger.info('Writing thinking response to file', {\n    path: config.outputPath.thinking.output,\n    content: thinking\n  });\n  \n  await fs.writeFile(config.outputPath.thinking.output, thinking);\n  logger.debug('Thinking phase complete');\n\n  // Phase 2: Response\n  logger.debug('Starting response phase');\n  const responseContext = {\n    ...config.context,\n    thinking,\n  };\n  \n  const responsePrompt = await renderTemplate(\n    config.responsePromptTemplate,\n    responseContext,\n    config.fileContext,\n    config.modelConfig?.useXML,\n    true // This is an LLM response\n  );\n  \n  // Save response prompt if requested or in dry run mode\n  if ((config.savePrompts || config.dryRun) && config.outputPath.response.prompt) {\n    await fs.mkdir(path.dirname(config.outputPath.response.prompt), { recursive: true });\n    await fs.writeFile(config.outputPath.response.prompt, responsePrompt);\n    logger.debug('Saved response prompt', { path: config.outputPath.response.prompt });\n  }\n  \n  logger.info('Sending response prompt to Claude...', {\n    role: config.role,\n    phase: config.phase,\n    contextKeys: Object.keys(responseContext),\n    outputPath: config.outputPath.response.output,\n    dryRun: config.dryRun\n  });\n  \n  const response = await claude.respond(responsePrompt, {\n    model: config.modelConfig?.responseModel,\n    temperature: config.modelConfig?.temperature,\n    maxTokens: config.modelConfig?.maxTokens,\n    useXML: config.modelConfig?.useXML,\n    savePrompt: config.savePrompts || config.dryRun,\n    dryRun: config.dryRun,\n    apiDryRun: config.apiDryRun,\n    useHaikuModel: config.useHaikuModel,\n    outputPath: config.outputPath.response.output\n  });\n\n  logger.info('Received response from Claude', {\n    responseLength: response?.length || 0,\n    firstChars: response?.substring(0, 100),\n    dryRun: config.dryRun\n  });\n  \n  logger.info('Writing response to file', {\n    path: config.outputPath.response.output,\n    content: response\n  });\n  \n  await fs.writeFile(config.outputPath.response.output, response);\n  logger.debug('Response phase complete');\n\n  return {\n    thinking,\n    response,\n  };\n}\n\n/**\n * Validates the two-phase processor configuration\n * @param config Configuration to validate\n * @throws Error if the configuration is invalid\n */\nexport function validateConfig(config: TwoPhaseProcessorConfig): void {\n  if (!config.role) {\n    throw new Error('Role is required in two-phase processor config');\n  }\n  \n  if (!['service', 'architect', 'pm'].includes(config.role)) {\n    throw new Error(`Invalid role: ${config.role}. Must be 'service', 'architect', or 'pm'`);\n  }\n  \n  if (!config.phase) {\n    throw new Error('Phase is required in two-phase processor config');\n  }\n  \n  if (!config.thinkingPromptTemplate) {\n    throw new Error('Thinking prompt template is required in two-phase processor config');\n  }\n  \n  if (!config.responsePromptTemplate) {\n    throw new Error('Response prompt template is required in two-phase processor config');\n  }\n  \n  if (!config.context) {\n    throw new Error('Context is required in two-phase processor config');\n  }\n  \n  if (!config.outputPath) {\n    throw new Error('Output path is required in two-phase processor config');\n  }\n  \n  if (!config.outputPath.thinking) {\n    throw new Error('Thinking output path is required in two-phase processor config');\n  }\n  \n  if (!config.outputPath.thinking.output) {\n    throw new Error('Thinking output file path is required in two-phase processor config');\n  }\n  \n  if (!config.outputPath.response) {\n    throw new Error('Response output path is required in two-phase processor config');\n  }\n  \n  if (!config.outputPath.response.output) {\n    throw new Error('Response output file path is required in two-phase processor config');\n  }\n} ",
    "filename": "processors/two-phase.ts"
  },
  {
    "content": "awaitingHumanInput: false\nfilesAwaitingReview: []\nprogress: []\nprocessedHumanInputPaths: []\ncontext: {}\n",
    "filename": "resume.yaml"
  },
  {
    "content": "import path from 'path';\nimport { ComponentRegistry } from './core/components/registry.js';\nimport { WorkflowExecutor } from './core/execution/workflow-executor.js';\nimport { loadResumeState } from './core/state/state.js';\nimport { logger } from './utils/logger.js';\n\ninterface RunOptions {\n  workflowDir: string;\n  savePrompts: boolean;\n  dryRun: boolean;\n  apiDryRun: boolean;\n  useHaikuModel: boolean;\n}\n\n/**\n * Run a workflow with the given options\n */\nexport async function run(workflowPath: string, options: RunOptions): Promise<void> {\n  const workflowDir = path.resolve(workflowPath);\n  const registry = new ComponentRegistry(workflowDir);\n  const workflow = await registry.loadWorkflow('.');\n\n  const executor = new WorkflowExecutor({\n    registry,\n    workflowPath: workflowDir,\n    savePrompts: options.savePrompts,\n    dryRun: options.dryRun,\n    apiDryRun: options.apiDryRun,\n    useHaikuModel: options.useHaikuModel\n  });\n\n  try {\n    const state = await loadResumeState(workflowDir);\n    if (state) {\n      logger.info('Found resume state, continuing workflow execution...');\n      await executor.executeWorkflow(workflow, true);\n    } else {\n      logger.info('Starting new workflow execution...');\n      await executor.executeWorkflow(workflow);\n    }\n\n    logger.info('Workflow completed successfully!');\n  } catch (error) {\n    if (error instanceof Error && error.message === 'Human input required') {\n      logger.info('Workflow paused - human input required.');\n      logger.info('The following files need review:');\n\n      const state = await loadResumeState(workflowDir);\n      if (state && state.filesAwaitingReview) {\n        state.filesAwaitingReview.forEach((file: string, index: number) => {\n          logger.info(`${index + 1}. ${file}`);\n        });\n      }\n    } else {\n      logger.error('Workflow failed:', error);\n      throw error;\n    }\n  }\n} ",
    "filename": "run.ts"
  },
  {
    "content": "# Template Engine for Committee\n\nThis module implements the template engine for the Committee project, providing context management, file collection handling, and template rendering.\n\n## Components\n\n### Context Management (`context.ts`)\n\nThe context management system provides a structured way to pass data between processing phases:\n\n- `TemplateContext` interface defines the structure for context objects\n- `createContext()` creates new context objects\n- `enhanceContext()` merges new context data into existing context\n- `enhanceContextWithPhaseOutput()` intelligently adds phase outputs to context\n- `createServiceContext()` creates service-specific context\n\n### File Collection Management (`collections.ts`)\n\nThe file collection management system extracts and manages collections of files from configuration:\n\n- `FileCollectionManager` class handles registration and retrieval of file collections\n- Automatically extracts collections from configuration objects\n- Supports filtering collections with glob-like patterns\n- Caches file content for performance\n\n### Template Rendering (`renderer.ts`)\n\nThe template renderer provides a comprehensive system for rendering templates with context and file collections:\n\n- `TemplateRenderer` class handles all aspects of template rendering\n- Built on Mustache for template variable substitution\n- Supports file collections with XML-based rendering\n- Supports filtering collections with patterns like `{{collection:*.ts}}`\n- Caches template and file content for performance\n\n## Usage\n\n### Basic Template Rendering\n\n```typescript\nimport { createContext, TemplateRenderer } from './templates/index.js';\n\n// Create context\nconst context = createContext();\ncontext.directiveName = 'embed';\ncontext.serviceName = 'ParserService';\n\n// Create renderer\nconst renderer = new TemplateRenderer(context);\n\n// Render a template file\nconst result = await renderer.renderTemplate('templates/requirements-thinking.md');\n```\n\n### Using File Collections\n\n```typescript\nimport { createContext, TemplateRenderer, FileCollectionManager } from './templates/index.js';\n\n// Create context\nconst context = createContext();\n\n// Create file collection manager and extract collections from config\nconst collectionManager = new FileCollectionManager();\ncollectionManager.extractCollectionsFromConfig(config);\n\n// Create renderer and register collections\nconst renderer = new TemplateRenderer(context);\nrenderer.registerCollections(collectionManager.getAllCollections());\n\n// Render template with file collections\nconst result = await renderer.renderTemplate('templates/draft-spec-thinking.md');\n```\n\n## File Collection Syntax\n\nTemplates can reference file collections using the standard Mustache syntax:\n\n```\nHere are all code files:\n{{code}}\n\nHere are only TypeScript files:\n{{code:*.ts}}\n```\n\nWhen rendered, file collections are wrapped in XML tags:\n\n```\n<code>\n  <path/to/file1.ts>\n  file content\n  </path/to/file1.ts>\n  \n  <path/to/file2.ts>\n  file content\n  </path/to/file2.ts>\n</code>\n```\n\nThis XML format works well with LLM-based processing, allowing the model to understand the structure of the files.\n\n## Testing\n\nYou can run the template test script to see the template engine in action:\n\n```\nnpm run test:templates\n```\n\nThis creates a sample template and renders it with test context and file collections. ",
    "filename": "templates/README.md"
  },
  {
    "content": "import { readFile } from '../utils/fs.ts';\nimport logger from '../utils/logger.ts';\nimport path from 'path';\n\n/**\n * Manages file collections extracted from configuration\n */\nexport class FileCollectionManager {\n  private collections: Record<string, string[]> = {};\n  private contentCache: Map<string, string> = new Map();\n  \n  /**\n   * Creates a new file collection manager\n   */\n  constructor() {\n    logger.debug('Created file collection manager');\n  }\n  \n  /**\n   * Registers a file collection\n   * @param name Collection name\n   * @param filePaths Array of file paths\n   */\n  registerCollection(name: string, filePaths: string[]): void {\n    this.collections[name] = filePaths;\n    logger.debug(`Registered file collection: ${name}`, {\n      fileCount: filePaths.length,\n      paths: filePaths \n    });\n  }\n  \n  /**\n   * Checks if a collection with the given name exists\n   * @param name Collection name\n   * @returns True if collection exists\n   */\n  hasCollection(name: string): boolean {\n    return name in this.collections;\n  }\n  \n  /**\n   * Gets files from a collection, optionally filtered\n   * @param name Collection name\n   * @param filterPattern Optional filter pattern\n   * @returns Array of matching file paths\n   */\n  getFiles(name: string, filterPattern?: string): string[] {\n    if (!this.hasCollection(name)) {\n      return [];\n    }\n    \n    const files = this.collections[name];\n    \n    if (!filterPattern) {\n      return files;\n    }\n    \n    // Apply simple glob pattern matching\n    return this.filterFilesByPattern(files, filterPattern);\n  }\n  \n  /**\n   * Gets all registered collection names\n   * @returns Array of collection names\n   */\n  getCollectionNames(): string[] {\n    return Object.keys(this.collections);\n  }\n  \n  /**\n   * Gets all collections as a record\n   * @returns Collections record\n   */\n  getAllCollections(): Record<string, string[]> {\n    return { ...this.collections };\n  }\n  \n  /**\n   * Extracts file collections from a configuration object\n   * @param config Configuration object\n   * @param basePath Optional base path for relative paths\n   */\n  extractCollectionsFromConfig(config: Record<string, any>, basePath?: string): void {\n    this.walkConfigForCollections(config, basePath);\n    logger.debug('Extracted collections from config', {\n      collectionCount: Object.keys(this.collections).length,\n      collectionNames: Object.keys(this.collections)\n    });\n  }\n  \n  /**\n   * Recursively walks a configuration object to find file collections\n   * @param obj Configuration object or sub-object\n   * @param basePath Optional base path for relative paths\n   * @param path Current path in the configuration (for nested objects)\n   */\n  private walkConfigForCollections(\n    obj: Record<string, any>,\n    basePath?: string,\n    objPath: string[] = []\n  ): void {\n    for (const [key, value] of Object.entries(obj)) {\n      const currentPath = [...objPath, key];\n      \n      // Check if this is an array of strings (potential file paths)\n      if (Array.isArray(value) && value.every(item => typeof item === 'string')) {\n        // This looks like an array of file paths\n        const collectionName = key;\n        \n        // Resolve paths relative to base path if provided\n        const filePaths = basePath \n          ? value.map(p => this.resolvePath(p, basePath))\n          : value;\n        \n        this.registerCollection(collectionName, filePaths);\n      }\n      \n      // Recurse into nested objects\n      if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n        this.walkConfigForCollections(value, basePath, currentPath);\n      }\n    }\n  }\n  \n  /**\n   * Resolves a potentially relative path against a base path\n   * @param filePath Path to resolve\n   * @param basePath Base path\n   * @returns Resolved path\n   */\n  private resolvePath(filePath: string, basePath: string): string {\n    if (path.isAbsolute(filePath)) {\n      return filePath;\n    }\n    return path.resolve(basePath, filePath);\n  }\n  \n  /**\n   * Filters files by a glob-like pattern\n   * @param files Array of file paths\n   * @param pattern Glob-like pattern\n   * @returns Filtered array of file paths\n   */\n  private filterFilesByPattern(files: string[], pattern: string): string[] {\n    // Convert glob pattern to regex\n    const regexPattern = pattern\n      .replace(/\\./g, '\\\\.')       // Escape periods\n      .replace(/\\*\\*/g, '.*')      // ** becomes .* (any characters)\n      .replace(/\\*/g, '[^/]*')     // * becomes [^/]* (any characters except /)\n      .replace(/\\?/g, '[^/]');     // ? becomes [^/] (any single character except /)\n    \n    const regex = new RegExp(regexPattern);\n    \n    return files.filter(file => regex.test(file));\n  }\n  \n  /**\n   * Reads content from a file with caching\n   * @param filePath Path to the file\n   * @returns File content\n   */\n  async getFileContent(filePath: string): Promise<string> {\n    if (this.contentCache.has(filePath)) {\n      return this.contentCache.get(filePath)!;\n    }\n    \n    try {\n      const content = await readFile(filePath);\n      this.contentCache.set(filePath, content);\n      return content;\n    } catch (error) {\n      logger.error(`Failed to read file: ${filePath}`, { error });\n      throw new Error(`Unable to read file ${filePath}: ${(error as Error).message}`);\n    }\n  }\n} ",
    "filename": "templates/collections.ts"
  },
  {
    "content": "import logger from '../utils/logger.ts';\n\n/**\n * Base template context interface - only contains core system properties\n * All workflow-specific variables are handled through the index signature\n */\nexport interface TemplateContext {\n  /**\n   * Name of the current phase being executed\n   */\n  phaseName?: string;\n\n  /**\n   * Output data from the current phase\n   */\n  phaseOutput?: Record<string, any>;\n\n  /**\n   * Extension point for workflow-specific variables\n   */\n  [key: string]: any;\n}\n\n/**\n * Creates a new empty context object\n * @returns Empty template context\n */\nexport function createContext(): TemplateContext {\n  return {};\n}\n\n/**\n * Merges a partial context into an existing context\n * @param baseContext The base context to enhance\n * @param newContext The new context to merge in\n * @returns Enhanced context object\n */\nexport function enhanceContext(\n  baseContext: TemplateContext,\n  newContext: Partial<TemplateContext>\n): TemplateContext {\n  logger.debug('Enhancing context', {\n    baseKeys: Object.keys(baseContext),\n    newKeys: Object.keys(newContext)\n  });\n  \n  return {\n    ...baseContext,\n    ...newContext\n  };\n}\n\n/**\n * Enhances context with phase output and maps required outputs\n * @param baseContext Base context object\n * @param phaseName Name of the phase\n * @param output Output data from the phase\n * @returns Enhanced context\n */\nexport function enhanceContextWithPhaseOutput(\n  baseContext: TemplateContext,\n  phaseName: string,\n  output: Record<string, any>\n): TemplateContext {\n  logger.debug(`Enhancing context with ${phaseName} output`, {\n    outputKeys: Object.keys(output)\n  });\n  \n  // Create enhanced context\n  const enhancedContext = { ...baseContext };\n  \n  // Store phase output in the core system property\n  enhancedContext.phaseOutput = output;\n  \n  // Store phase name\n  enhancedContext.phaseName = phaseName;\n  \n  // If there are required outputs specified in the task, map them directly\n  if (output.requiredOutputs) {\n    Object.entries(output.requiredOutputs).forEach(([key, value]) => {\n      enhancedContext[key] = value;\n    });\n  }\n  \n  return enhancedContext;\n} ",
    "filename": "templates/context.ts"
  },
  {
    "content": "// Export template context management\nexport * from './context.ts';\n\n// Export template renderer\nexport * from './renderer.ts';\n\n// Export file collection manager\nexport * from './collections.ts';\n\n// Re-export any necessary functions from utils/template\nexport * from '../utils/template.ts'; ",
    "filename": "templates/index.ts"
  },
  {
    "content": "name: \"draft-spec-creation\"\ndescription: \"Create draft specification from service requirements\"\nexecution: \"sequential\"\nhumanInputRequired: []\nset:\n  - description: \"Synthesize requirements into draft specification\"\n    useSet: \"synthesis\"\n    requiredInput: [\"directiveName\", \"serviceRequirements\"]\n    requiredOutput: [\"draftSpecification\"]\n\nrequiredInput: [\"directiveName\", \"serviceRequirements\"]\nrequiredOutput: [\"draftSpecification\"]\ndependsOn: [\"requirements-collection\"] ",
    "filename": "templates/phases/draft-spec-creation.phase.yaml"
  },
  {
    "content": "name: \"feedback-collection\"\ndescription: \"Collect feedback from all services in parallel\"\nexecution: \"sequential\"\nhumanInputRequired: []\nset:\n  - description: \"Collect feedback from each service in parallel\"\n    template: \"service-feedback\"\n    execution: \"parallel\"\n    for_each: \"services\"\n    variables:\n      serviceName: \"{{item.name}}\"\n      serviceDescription: \"{{item.description}}\"\n    requiredInput: [\"directiveName\", \"serviceRequirements\", \"draftSpecification\"]\n    requiredOutput: [\"individualServiceFeedback\"]\n    \n  - description: \"Consolidate all service feedback\"\n    useSet: \"feedback-consolidation\"\n    requiredInput: [\"individualServiceFeedback\"]\n    requiredOutput: [\"serviceFeedback\"]\n    variables:\n      outputPath: \"feedback/consolidated\"\n\nrequiredInput: [\"directiveName\", \"services\", \"serviceRequirements\", \"draftSpecification\"]\nrequiredOutput: [\"serviceFeedback\"]\ndependsOn: [\"review\"] ",
    "filename": "templates/phases/feedback-collection.phase.yaml"
  },
  {
    "content": "name: \"final-review\"\ndescription: \"Final human review of specification\"\nexecution: \"sequential\"\nhumanInputRequired: [\"examples-output/complex/final-spec-creation/final-spec/finalSpecification.o.md\"]\nset: []\n\nrequiredInput: [\"finalSpecification\"]\nrequiredOutput: []\ndependsOn: [\"final-spec-creation\"] ",
    "filename": "templates/phases/final-review.phase.yaml"
  },
  {
    "content": "name: \"final-spec-creation\"\ndescription: \"Create final specification incorporating feedback\"\nexecution: \"sequential\"\nhumanInputRequired: []\nset:\n  - description: \"Create final specification\"\n    useSet: \"final-spec\"\n    requiredInput: [\"directiveName\", \"draftSpecification\", \"serviceFeedback\"]\n    requiredOutput: [\"finalSpecification\"]\n\nrequiredInput: [\"directiveName\", \"draftSpecification\", \"serviceFeedback\"]\nrequiredOutput: [\"finalSpecification\"]\ndependsOn: [\"feedback-collection\"] ",
    "filename": "templates/phases/final-spec-creation.phase.yaml"
  },
  {
    "content": "name: \"requirements-collection\"\ndescription: \"Collect requirements from all services in parallel\"\nexecution: \"sequential\"\nhumanInputRequired: []\nset:\n  - description: \"Collect requirements from each service in parallel\"\n    template: \"service-requirements\"\n    execution: \"parallel\"\n    for_each: \"services\"\n    variables:\n      serviceName: \"{{item.name}}\"\n      serviceDescription: \"{{item.description}}\"\n    requiredInput: [\"directiveName\"]\n    requiredOutput: [\"serviceRequirements\"]\n\nrequiredInput: [\"directiveName\", \"services\"]\nrequiredOutput: [\"serviceRequirements\"] ",
    "filename": "templates/phases/requirements-collection.phase.yaml"
  },
  {
    "content": "name: \"review\"\ndescription: \"Human review of draft specification\"\nexecution: \"sequential\"\nhumanInputRequired: [\"examples-output/complex/draft-spec-creation/synthesis/draft-specification.o.md\"]\nset:\n  - description: \"Review draft specification\"\n    useSet: \"draft-review\"\n    requiredInput: [\"directiveName\", \"draftSpecification\"]\n    requiredOutput: [\"draftSpecification\"]\n\nrequiredInput: [\"directiveName\", \"draftSpecification\"]\nrequiredOutput: [\"draftSpecification\"]\ndependsOn: [\"draft-spec-creation\"] ",
    "filename": "templates/phases/review.phase.yaml"
  },
  {
    "content": "import path from 'path';\nimport Mustache from 'mustache';\nimport { readFile, fileExists, resolvePath } from '../utils/fs.ts';\nimport logger from '../utils/logger.ts';\nimport * as contextModule from './context.ts';\nimport { renderTemplate as basicRenderTemplate } from '../utils/template.ts';\nimport * as llmxml from '../utils/llmxml.ts';\n\n// Cache to avoid loading the same files multiple times\nconst fileContentCache = new Map<string, string>();\n\n/**\n * Represents a file collection for template rendering\n */\nexport interface FileCollection {\n  name: string;\n  files: string[];\n}\n\n/**\n * Template renderer that supports file collections and context management\n */\nexport class TemplateRenderer {\n  private collections: Record<string, string[]> = {};\n  private context: contextModule.TemplateContext;\n  private templateDir: string;\n  private useXML: boolean;\n  \n  /**\n   * Creates a new template renderer\n   * @param context Initial context\n   * @param templateDir Optional template directory path\n   * @param useXML Whether to convert templates to XML format\n   */\n  constructor(context: contextModule.TemplateContext = {}, templateDir?: string, useXML: boolean = false) {\n    this.context = context;\n    this.templateDir = templateDir || process.env.TEMPLATE_DIR || 'templates';\n    this.useXML = useXML;\n    logger.debug('Created template renderer', { \n      templateDir: this.templateDir,\n      initialContextKeys: Object.keys(context),\n      useXML: this.useXML\n    });\n  }\n  \n  /**\n   * Updates the template context\n   * @param newContext New context values to merge\n   */\n  updateContext(newContext: Partial<contextModule.TemplateContext>): void {\n    this.context = { ...this.context, ...newContext };\n    logger.debug('Updated template context', { \n      contextKeys: Object.keys(this.context)\n    });\n  }\n  \n  /**\n   * Registers a file collection for use in templates\n   * @param name Collection name\n   * @param files Array of file paths\n   */\n  registerCollection(name: string, files: string[]): void {\n    this.collections[name] = files;\n    logger.debug(`Registered file collection: ${name}`, {\n      fileCount: files.length\n    });\n  }\n  \n  /**\n   * Registers multiple file collections\n   * @param collections Record of collection names to file arrays\n   */\n  registerCollections(collections: Record<string, string[]>): void {\n    for (const [name, files] of Object.entries(collections)) {\n      this.registerCollection(name, files);\n    }\n  }\n  \n  /**\n   * Loads a template file from the template directory\n   * @param templateName Template name/path relative to template dir\n   * @returns Template content\n   */\n  async loadTemplate(templateName: string): Promise<string> {\n    const templatePath = path.join(this.templateDir, templateName);\n    \n    if (fileContentCache.has(templatePath)) {\n      logger.debug(`Using cached template: ${templatePath}`);\n      return fileContentCache.get(templatePath)!;\n    }\n    \n    try {\n      logger.debug(`Loading template: ${templatePath}`);\n      const content = await readFile(templatePath);\n      fileContentCache.set(templatePath, content);\n      return content;\n    } catch (error) {\n      logger.error(`Failed to load template: ${templatePath}`, { error });\n      throw new Error(`Unable to load template ${templateName}: ${(error as Error).message}`);\n    }\n  }\n  \n  /**\n   * Renders a template string with the current context\n   * @param template Template string\n   * @param options Optional rendering options\n   * @returns Rendered content\n   */\n  async render(template: string, options?: { isLLMResponse?: boolean }): Promise<string> {\n    try {\n      // First resolve any file collections\n      const resolvedTemplate = await this.resolveFileCollections(template);\n      \n      // Then render with Mustache\n      // Disable HTML escaping as we're working with markdown\n      Mustache.escape = (text) => text;\n      \n      logger.debug('Rendering template with context', { \n        contextKeys: Object.keys(this.context),\n        collectionKeys: Object.keys(this.collections),\n        isLLMResponse: options?.isLLMResponse\n      });\n\n      // Split template into frontmatter and content if it exists\n      const frontmatterMatch = resolvedTemplate.match(/^(---\\n[\\s\\S]+?\\n---\\n|{\\n[\\s\\S]+?\\n}\\n)([\\s\\S]*)$/);\n      \n      if (frontmatterMatch) {\n        const [, frontmatter, content] = frontmatterMatch;\n        \n        // Render frontmatter with raw context (no escaping)\n        const renderedFrontmatter = Mustache.render(frontmatter, this.context);\n        \n        // Create safe context for content rendering\n        const safeContext = this.createSafeContext(this.context);\n        \n        // Render content with safe context but don't escape remaining tags\n        const renderedContent = Mustache.render(content, safeContext);\n        \n        return renderedFrontmatter + renderedContent;\n      }\n      \n      // No frontmatter - create safe context and handle content\n      const safeContext = this.createSafeContext(this.context);\n      \n      // Render with safe context but don't escape remaining tags\n      return Mustache.render(resolvedTemplate, safeContext);\n    } catch (error) {\n      logger.error('Failed to render template', { error });\n      throw new Error(`Template rendering failed: ${(error as Error).message}`);\n    }\n  }\n  \n  /**\n   * Renders a template file with the current context\n   * @param templateName Name of the template file\n   * @param options Optional rendering options\n   * @returns Rendered content\n   */\n  async renderTemplate(templateName: string, options?: { isLLMResponse?: boolean }): Promise<string> {\n    try {\n      logger.debug(`Loading template: ${templateName}`);\n      \n      // Load template content\n      const templatePath = path.join(this.templateDir, templateName);\n      const template = await readFile(templatePath);\n      \n      // Render the template\n      return this.render(template, options);\n    } catch (error) {\n      logger.error(`Failed to render template: ${templateName}`, { error });\n      throw new Error(`Template rendering failed: ${(error as Error).message}`);\n    }\n  }\n  \n  /**\n   * Processes a response from the LLM, converting from XML if needed\n   * @param response Raw response from LLM\n   * @param template Optional template to render response with\n   * @returns Processed response\n   */\n  async processResponse(response: string, template?: string): Promise<string> {\n    try {\n      // First convert from XML if needed\n      let processedResponse = response;\n      if (this.useXML) {\n        logger.debug('Converting response from XML format');\n        processedResponse = await llmxml.toMarkdown(response);\n      }\n\n      // If a template is provided, render it with the response\n      if (template) {\n        logger.debug('Rendering response with template');\n        const templateContent = await this.loadTemplate(template);\n        return this.render(templateContent, { isLLMResponse: true });\n      }\n\n      return processedResponse;\n    } catch (error) {\n      logger.error('Failed to process response', { error });\n      throw new Error(`Response processing failed: ${(error as Error).message}`);\n    }\n  }\n  \n  /**\n   * Resolves file collections in a template string\n   * @param template Template string with {{collection}} references\n   * @returns Template with collections resolved to markdown content blocks\n   */\n  private async resolveFileCollections(template: string): Promise<string> {\n    // Find all variable references in the template\n    const variableRegex = /\\{\\{([^}:]+)(?::([^}]+))?\\}\\}/g;\n    const variables = [...template.matchAll(variableRegex)].map(match => ({\n      full: match[0],\n      name: match[1].trim(),\n      filter: match[2]?.trim(),\n    }));\n    \n    let resolvedTemplate = template;\n    \n    // Process each variable that might be a file collection\n    for (const variable of variables) {\n      const { name, filter } = variable;\n      \n      // Check if this variable refers to a file collection\n      if (this.collections[name]) {\n        logger.debug(`Resolving file collection: ${name}${filter ? ' with filter: ' + filter : ''}`);\n        \n        // Get the matching files (apply filter if any)\n        const filePaths = filter \n          ? this.applyFilter(this.collections[name], filter)\n          : this.collections[name];\n        \n        if (filePaths.length === 0) {\n          const replacement = `<!-- No matching files for ${name}${filter ? ': ' + filter : ''} -->`;\n          resolvedTemplate = resolvedTemplate.replace(variable.full, replacement);\n          continue;\n        }\n        \n        // Build markdown content for the file collection\n        const contentParts = [];\n        \n        // Add a section header for the collection (H1)\n        contentParts.push(`# ${name}`);\n        contentParts.push('');\n        \n        for (const filePath of filePaths) {\n          try {\n            const content = await this.loadFileContent(filePath);\n            const fileName = path.basename(filePath);\n            \n            // Add a header for each file (H2)\n            contentParts.push(`## ${fileName}`);\n            contentParts.push('');\n            \n            // Determine language for code block based on file extension\n            const ext = path.extname(fileName).slice(1);\n            const language = this.getLanguageForExtension(ext);\n            \n            // Add code block with language identifier when appropriate\n            contentParts.push('```' + language);\n            contentParts.push(content);\n            contentParts.push('```');\n            contentParts.push('');\n          } catch (error) {\n            logger.warn(`Failed to load file for collection: ${filePath}`, { error });\n            contentParts.push(`## ${path.basename(filePath)}`);\n            contentParts.push('');\n            contentParts.push(`<!-- Error loading file: ${(error as Error).message} -->`);\n            contentParts.push('');\n          }\n        }\n        \n        const replacement = contentParts.join('\\n');\n        \n        // Replace the variable with the collection content\n        resolvedTemplate = resolvedTemplate.replace(variable.full, replacement);\n      }\n    }\n    \n    return resolvedTemplate;\n  }\n  \n  /**\n   * Get the language identifier for code blocks based on file extension\n   * @param extension File extension\n   * @returns Language identifier for code blocks\n   */\n  private getLanguageForExtension(extension: string): string {\n    const languageMap: Record<string, string> = {\n      // Programming languages\n      'js': 'javascript',\n      'ts': 'typescript',\n      'jsx': 'jsx',\n      'tsx': 'tsx',\n      'py': 'python',\n      'java': 'java',\n      'rb': 'ruby',\n      'php': 'php',\n      'go': 'go',\n      'rs': 'rust',\n      'c': 'c',\n      'cpp': 'cpp',\n      'cs': 'csharp',\n      'swift': 'swift',\n      'kt': 'kotlin',\n      \n      // Markup and config\n      'html': 'html',\n      'css': 'css',\n      'scss': 'scss',\n      'json': 'json',\n      'yaml': 'yaml',\n      'yml': 'yaml',\n      'xml': 'xml',\n      'md': 'markdown',\n      'sh': 'bash',\n      'bash': 'bash',\n      'sql': 'sql',\n      \n      // Default to empty string for unknown extensions\n      '': ''\n    };\n    \n    return languageMap[extension.toLowerCase()] || '';\n  }\n  \n  /**\n   * Applies a filter pattern to a list of file paths\n   * @param files Array of file paths\n   * @param filter Filter pattern (glob-like)\n   * @returns Filtered array of file paths\n   */\n  private applyFilter(files: string[], filter: string): string[] {\n    // Support basic glob patterns by converting to regex\n    const regexPattern = filter\n      .replace(/\\./g, '\\\\.')       // Escape periods\n      .replace(/\\*\\*/g, '.*')      // ** becomes .* (any characters)\n      .replace(/\\*/g, '[^/]*')     // * becomes [^/]* (any characters except /)\n      .replace(/\\?/g, '[^/]');     // ? becomes [^/] (any single character except /)\n    \n    const regex = new RegExp(regexPattern);\n    \n    return files.filter(file => regex.test(file));\n  }\n  \n  /**\n   * Loads content from a file with caching\n   * @param filePath Path to the file\n   * @returns File content\n   */\n  private async loadFileContent(filePath: string): Promise<string> {\n    if (fileContentCache.has(filePath)) {\n      return fileContentCache.get(filePath)!;\n    }\n    \n    const content = await readFile(filePath);\n    fileContentCache.set(filePath, content);\n    return content;\n  }\n\n  /**\n   * Creates a safe context for template rendering\n   * @param context Original context object\n   * @returns Safe context object\n   */\n  private createSafeContext(context: Record<string, any>): Record<string, any> {\n    const safeContext: Record<string, any> = {};\n    \n    for (const [key, value] of Object.entries(context)) {\n      if (typeof value === 'string') {\n        // Don't escape Mustache tags in normal template rendering\n        safeContext[key] = value;\n      } else if (value && typeof value === 'object') {\n        // Recursively process nested objects\n        safeContext[key] = this.createSafeContext(value);\n      } else {\n        // Pass through non-string values unchanged\n        safeContext[key] = value;\n      }\n    }\n    \n    return safeContext;\n  }\n  \n  /**\n   * Escapes mustache tags in a string by adding a zero-width space\n   * Only used for LLM responses where we want to preserve the tags\n   * @param text Text containing mustache tags to escape\n   * @returns Text with escaped mustache tags\n   */\n  private escapeMustacheTags(text: string): string {\n    // Only escape if the text appears to be an LLM response with template tags\n    if (text.includes('{{') && text.includes('}}')) {\n      return text.replace(/{{/g, '{​{').replace(/}}/g, '}​}');\n    }\n    return text;\n  }\n} ",
    "filename": "templates/renderer.ts"
  },
  {
    "content": "name: \"draft-review\"\ndescription: \"Review draft specification\"\nexecution: \"sequential\"\ntasks:\n  - description: \"Review draft specification\"\n    useTask: \"draft-review\"\n    variables:\n      role: \"architect\"\n\nrequiredInput: [\"directiveName\", \"draftSpecification\"]\nrequiredOutput: [\"draftSpecification\"] ",
    "filename": "templates/sets/draft-review.set.yaml"
  },
  {
    "content": "name: \"feedback-consolidation\"\ndescription: \"Consolidate feedback from all services\"\nexecution: \"sequential\"\ntasks:\n  - description: \"Consolidate service feedback\"\n    useTask: \"consolidate-feedback\"\n    variables:\n      role: \"architect\"\n\nrequiredInput: [\"individualServiceFeedback\"]\nrequiredOutput: [\"serviceFeedback\"] ",
    "filename": "templates/sets/feedback-consolidation.set.yaml"
  },
  {
    "content": "name: \"final-spec\"\ndescription: \"Create final specification after incorporating service feedback\"\nexecution: \"sequential\"\ntasks:\n  - description: \"Generate final specification\"\n    useTask: \"final-specification\"\n    variables:\n      role: \"architect\"\n\nrequiredInput: [\"directiveName\", \"draftSpecification\", \"serviceFeedback\"]\nrequiredOutput: [\"finalSpecification\"] ",
    "filename": "templates/sets/final-spec.set.yaml"
  },
  {
    "content": "name: \"service-feedback\"\ndescription: \"Collect feedback on draft specification from a service\"\nexecution: \"sequential\"\ntasks:\n  - description: \"Think about draft specification review\"\n    useTask: \"feedback-thinking\"\n    variables:\n      role: \"service\"\n  \n  - description: \"Generate formal feedback response\"\n    useTask: \"feedback-response\"\n    variables:\n      role: \"service\"\n\nrequiredInput: [\"directiveName\", \"serviceName\", \"serviceDescription\", \"serviceRequirements\", \"draftSpecification\"]\nrequiredOutput: [\"individualServiceFeedback\"] ",
    "filename": "templates/sets/service-feedback.set.yaml"
  },
  {
    "content": "name: \"service-requirements\"\ndescription: \"Collect type requirements from a service\"\nexecution: \"sequential\"\ntasks:\n  - description: \"Think about type requirements\"\n    useTask: \"requirements-thinking\"\n    variables:\n      role: \"service\"\n      directiveName: \"{{directiveName}}\"\n      serviceName: \"{{serviceName}}\"\n      serviceDescription: \"{{serviceDescription}}\"\n  \n  - description: \"Generate formal requirements response\"\n    useTask: \"requirements-response\"\n    variables:\n      role: \"service\"\n      directiveName: \"{{directiveName}}\"\n      serviceName: \"{{serviceName}}\"\n      serviceDescription: \"{{serviceDescription}}\"\n      thinking: \"{{thinking}}\"\n\nrequiredInput: [\"directiveName\", \"serviceName\", \"serviceDescription\"]\nrequiredOutput: [\"serviceRequirements\"] ",
    "filename": "templates/sets/service-requirements.set.yaml"
  },
  {
    "content": "name: \"synthesis\"\ndescription: \"Synthesize service requirements into a draft specification\"\nexecution: \"sequential\"\ntasks:\n  - description: \"Think about requirements synthesis\"\n    useTask: \"synthesis-thinking\"\n    variables:\n      role: \"architect\"\n  \n  - description: \"Generate draft specification\"\n    useTask: \"synthesis-response\"\n    variables:\n      role: \"architect\"\n\nrequiredInput: [\"directiveName\", \"serviceRequirements\"]\nrequiredOutput: [\"draftSpecification\"] ",
    "filename": "templates/sets/synthesis.set.yaml"
  },
  {
    "content": "---\nname: \"consolidate-feedback\"\nrole: \"architect\"\nrequiredInput: [\"individualServiceFeedback\"]\nrequiredOutput: [\"serviceFeedback\"]\n---\n\n# Service Feedback Consolidation\n\n## Context\nYou are consolidating feedback from multiple services on the draft specification.\n\n## Service Feedback\nHere is the feedback received from each service:\n\n{{individualServiceFeedback}}\n\n## Task\nPlease consolidate the feedback from all services into a single, coherent document:\n\n1. Identify common themes and concerns across services\n2. Highlight any conflicting requirements or suggestions\n3. Prioritize changes based on impact and number of services affected\n4. Create a clear list of actionable modifications needed\n\n## Response Format\nStructure your consolidated feedback as follows:\n\n### Summary\nBrief overview of the feedback themes and key findings\n\n### Common Themes\nList of requirements and concerns shared across multiple services\n\n### Service-Specific Concerns\nImportant points raised by individual services that need consideration\n\n### Conflicts\nAny conflicting requirements or suggestions between services\n\n### Recommended Actions\nPrioritized list of changes to make to the specification\n\n### Additional Considerations\nAny other important points for the final specification ",
    "filename": "templates/tasks/consolidate-feedback.task.md"
  },
  {
    "content": "---\nname: \"draft-review\"\nrole: \"architect\"\nrequiredInput: [\"directiveName\", \"draftSpecification\"]\nrequiredOutput: [\"draftSpecification\"]\n---\n\n# {{directiveName}} Draft Specification Review\n\n## Context\nYou are reviewing the draft specification for {{directiveName}} types.\n\n## Draft Specification\n{{draftSpecification}}\n\n## Task\nPlease review the draft specification and provide feedback:\n\n1. Verify that the specification is complete and well-structured\n2. Check for any inconsistencies or potential issues\n3. Ensure the types are properly defined and relationships are clear\n4. Validate that validation rules are appropriate\n5. Consider extensibility and maintainability\n\n## Response Format\nStructure your review as follows:\n\n### Overall Assessment\nHigh-level evaluation of the specification\n\n### Strengths\nWhat works well in the current specification\n\n### Areas for Improvement\nWhat could be enhanced or clarified\n\n### Recommendations\nSpecific suggestions for improving the specification\n\n### Next Steps\nWhat should be done to move forward with this specification ",
    "filename": "templates/tasks/draft-review.task.md"
  },
  {
    "content": "---\nname: \"feedback-response\"\nrole: \"service\"\nrequiredInput: [\"directiveName\", \"serviceName\", \"serviceDescription\", \"serviceRequirements\", \"draftSpecification\", \"feedbackThinking\"]\nrequiredOutput: [\"individualServiceFeedback\"]\n---\n\n# {{directiveName}} Draft Specification Review - Response Phase\n\n## Context\nYou are providing formal feedback on the draft specification for {{directiveName}} types.\n\n## Service Context\nYou represent {{serviceName}}, which is responsible for {{serviceDescription}}.\n\n## Previous Analysis\nYou previously analyzed the draft specification and had these thoughts:\n\n{{feedbackThinking}}\n\n## Task\nBased on your previous analysis, provide formal feedback on the draft specification:\n\n1. List aspects of the specification that effectively address your requirements\n2. Identify any missing or incorrectly interpreted requirements\n3. Suggest specific modifications to the type definitions or interfaces\n4. Propose alternative approaches where appropriate\n5. Rate the overall suitability of the design for your service (1-10)\n\n## Response Format\nStructure your feedback clearly with:\n- Summary assessment\n- Positive aspects (what works well)\n- Areas for improvement (specific changes needed)\n- Code examples of suggested modifications\n- Implementation considerations from your service's perspective\n\nYour feedback should be constructive and solution-oriented, focusing on how to evolve the specification to better meet your service's needs. ",
    "filename": "templates/tasks/feedback-response.task.md"
  },
  {
    "content": "---\nname: \"feedback-thinking\"\nrole: \"service\"\nrequiredInput: [\"directiveName\", \"serviceName\", \"serviceDescription\", \"serviceRequirements\", \"draftSpecification\"]\nrequiredOutput: [\"feedbackThinking\"]\n---\n\n# {{directiveName}} Draft Specification Review - Thinking Phase\n\n## Context\nYou are reviewing the draft specification for {{directiveName}} types to evaluate how well it meets your service's needs.\n\n## Service Context\nYou are the lead developer for {{serviceName}}, which is responsible for {{serviceDescription}}.\n\n## Your Original Requirements\nYou previously submitted these requirements:\n\n{{serviceRequirements}}\n\n## Draft Specification\nThe architect has produced this draft specification:\n\n{{draftSpecification}}\n\n## Task\nThink through how well the draft specification meets your service's needs:\n\n1. Does it address all your critical requirements?\n2. Are there any misunderstandings or misinterpretations of your requirements?\n3. Will the proposed interfaces work well with your service implementation?\n4. What potential issues do you foresee with this design?\n5. Are there optimization opportunities or simplifications that could be made?\n\n## Reflection\nThink about both the immediate implementation concerns and longer-term maintainability. Consider your team's workflow and how this design would impact your code organization and complexity. Your goal is to provide constructive feedback that will help refine the specification. ",
    "filename": "templates/tasks/feedback-thinking.task.md"
  },
  {
    "content": "---\nname: \"final-specification\"\nrole: \"architect\"\nrequiredInput: [\"directiveName\", \"draftSpecification\", \"serviceFeedback\"]\nrequiredOutput: [\"finalSpecification\"]\n---\n\n# {{directiveName}} Final Type Specification\n\n## Context\nYou are the architect responsible for finalizing the {{directiveName}} type specification after incorporating service feedback.\n\n## Draft Specification\nYour previous draft specification:\n\n{{draftSpecification}}\n\n## Service Feedback\nThe service teams have provided the following feedback:\n\n{{serviceFeedback}}\n\n## Task\nCreate a final type specification for {{directiveName}} types that incorporates the feedback provided by service teams:\n\n1. Revise the type definitions and interfaces based on feedback\n2. Explain how the design has evolved from the draft specification\n3. Address all critical concerns raised in the feedback\n4. Document design decisions, especially where trade-offs were made\n5. Provide migration guidance for services adopting these new types\n\n## Response Format\nYour response should be structured as a comprehensive specification document with:\n- Executive summary of changes from draft\n- Complete and final TypeScript interfaces\n- Implementation guideline with examples\n- Validation rules\n- Migration guidance\n- Rationale for design decisions\n\nThis is the final specification that will be implemented, so ensure it is complete, clear, and addresses the needs of all services. ",
    "filename": "templates/tasks/final-specification.task.md"
  },
  {
    "content": "---\nname: \"requirements-response\"\nrole: \"service\"\nrequiredInput: [\"directiveName\", \"serviceName\", \"serviceDescription\", \"thinking\"]\nrequiredOutput: [\"serviceRequirements\"]\n---\n\n# {{directiveName}} Type Requirements - Response Phase\n\n## Context\nYou are finalizing your requirements for {{directiveName}} types.\n\n## Service Context\nYou are the lead developer for {{serviceName}}, which is responsible for {{serviceDescription}}.\n\n## Previous Thinking\nYou previously analyzed the requirements and came to these conclusions:\n\n{{thinking}}\n\n## Task\nBased on your previous analysis, create a formal list of requirements for {{directiveName}} types from your service's perspective:\n\n1. Provide a prioritized list of properties that should be included in {{directiveName}} types\n2. Specify any type discriminators that would be helpful\n3. Highlight integration points with other services\n4. Suggest validation rules for these types\n\n## Response Format\nStructure your response with clear headings and bullet points for each requirement category. Include code examples where appropriate to illustrate your points. Be concise but thorough. ",
    "filename": "templates/tasks/requirements-response.task.md"
  },
  {
    "content": "---\nname: \"requirements-thinking\"\nrole: \"service\"\nrequiredInput: [\"directiveName\", \"serviceName\", \"serviceDescription\"]\nrequiredOutput: [\"thinking\"]\n---\n\n# {{directiveName}} Type Requirements Analysis - Thinking Phase\n\n## Context\nYou are analyzing the requirements for {{directiveName}} types.\n\n## Service Context\nYou are the lead developer for {{serviceName}}, which is responsible for {{serviceDescription}}.\n\n## Task\nThink about what your service needs from the {{directiveName}} type system. Consider:\n\n1. What properties must exist in {{directiveName}} types?\n2. What pain points exist in the current implementation?\n3. How would more structured types improve your service's code?\n4. What type discriminators would make processing more robust?\n\n## Reflection\nBefore providing your final answer, reflect on:\n- Essential vs. nice-to-have properties\n- Implementation complexity\n- Cross-service impacts\n- Concrete use cases\n\nTake your time to consider all aspects thoroughly. This is a thinking phase, so explore different possibilities and their implications. ",
    "filename": "templates/tasks/requirements-thinking.task.md"
  },
  {
    "content": "---\nname: \"synthesis-response\"\nrole: \"architect\"\nrequiredInput: [\"directiveName\", \"serviceRequirements\", \"synthesisPlan\"]\nrequiredOutput: [\"draftSpecification\"]\n---\n\n# {{directiveName}} Requirements Synthesis - Response Phase\n\n## Context\nYou are the architect responsible for creating a draft specification based on collected requirements.\n\n## Previous Analysis\nYou previously analyzed the requirements and developed this synthesis plan:\n\n{{synthesisPlan}}\n\n## Service Requirements\n{{serviceRequirements}}\n\n## Task\nBased on your previous analysis, create a draft specification for {{directiveName}} types:\n\n1. Define the complete type structure with properties, types, and relations\n2. Provide TypeScript interfaces for all proposed types\n3. Explain design decisions, especially where trade-offs were made\n4. Include validation rules and constraints\n5. Address how this design satisfies each service's critical requirements\n\n## Response Format\nYour response should be structured as a formal specification document with:\n- Executive summary\n- Type definitions in TypeScript\n- Design rationale\n- Service requirement coverage\n- Implementation considerations\n\nThis draft will be reviewed by the service teams, so make sure it's clear, comprehensive, and justified. ",
    "filename": "templates/tasks/synthesis-response.task.md"
  },
  {
    "content": "---\nname: \"synthesis-thinking\"\nrole: \"architect\"\nrequiredInput: [\"directiveName\", \"serviceRequirements\"]\nrequiredOutput: [\"synthesisPlan\"]\n---\n\n# {{directiveName}} Requirements Synthesis - Thinking Phase\n\n## Context\nYou are the architect responsible for synthesizing service requirements into a cohesive type design.\n\n## Collected Requirements\nService teams have provided their requirements for {{directiveName}} types:\n\n{{serviceRequirements}}\n\n## Task\nThink through how to integrate these service requirements into a unified type design:\n\n1. Identify common patterns and requirements across services\n2. Note conflicts or contradictions in requirements\n3. Develop strategies for resolving conflicts\n4. Outline a basic type structure that satisfies most critical requirements\n\n## Reflection\nConsider architectural principles like:\n- Separation of concerns\n- Forward compatibility\n- Minimal complexity\n- Developer ergonomics\n\nYour goal in this thinking phase is to explore the design space and identify potential approaches to satisfying all services' needs. Focus on identifying tensions and potential solutions. ",
    "filename": "templates/tasks/synthesis-thinking.task.md"
  },
  {
    "content": "#!/usr/bin/env node\n\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { createContext, TemplateRenderer, FileCollectionManager } from './index.ts';\nimport { writeFile, ensureDir } from '../utils/fs.ts';\nimport logger from '../utils/logger.ts';\nimport fs from 'fs/promises';\n\n// Get the directory name\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst rootDir = path.resolve(__dirname, '../../');\n\n// Sample template for testing\nconst SAMPLE_TEMPLATE = `\n# Test Template\n\n## Context Variables\n- Directive Name: {{directiveName}}\n- Service Name: {{serviceName}}\n\n## File Collections\nHere are code files:\n{{code}}\n\nHere are document files:\n{{documents}}\n\n## Filtered Collections\nHere are TypeScript files:\n{{code:*.ts}}\n`;\n\n// Sample LLM response with mustache tags\nconst SAMPLE_LLM_RESPONSE = `\n# Generated Content\n\nHere's a variable: {{item.name}}\nAnd another: {{item.description}}\n`;\n\n// Make sure paths are absolute for testing\nconst makeAbsolute = (filePath: string): string => {\n  return path.isAbsolute(filePath) ? filePath : path.join(rootDir, filePath);\n};\n\n// Sample context\nconst sampleContext = {\n  directiveName: 'TestDirective',\n  serviceName: 'TestService'\n};\n\n/**\n * Runs a template engine test\n */\nasync function runTest() {\n  try {\n    logger.info('Starting template engine test');\n    \n    // Create test output directory\n    const outputDir = path.join(rootDir, '_dev/test');\n    await ensureDir(outputDir);\n    \n    // Create test template file\n    const templatePath = path.join(outputDir, 'test-template.md');\n    await writeFile(templatePath, SAMPLE_TEMPLATE);\n    logger.info(`Created test template at: ${templatePath}`);\n    \n    // Create mock source files for collections if they don't exist\n    const srcDir = path.join(outputDir, 'src');\n    await ensureDir(path.join(srcDir, 'utils'));\n    await ensureDir(path.join(srcDir, 'templates'));\n    \n    const files = [\n      { path: path.join(srcDir, 'index.ts'), content: 'export * from \"./utils/helpers.js\";\\n' },\n      { path: path.join(srcDir, 'utils/helpers.js'), content: 'export function helper() { return true; }\\n' },\n      { path: path.join(srcDir, 'templates/renderer.ts'), content: 'export class Renderer {}\\n' },\n      { path: path.join(outputDir, 'README.md'), content: '# Test Project\\n' },\n      { path: path.join(outputDir, 'docs/usage.md'), content: '# Usage Guide\\n' }\n    ];\n    \n    await ensureDir(path.join(outputDir, 'docs'));\n    \n    for (const file of files) {\n      await writeFile(file.path, file.content);\n      logger.debug(`Created mock file: ${file.path}`);\n    }\n    \n    // Define collections directly with absolute paths\n    const collections = {\n      code: [\n        path.join(srcDir, 'index.ts'),\n        path.join(srcDir, 'utils/helpers.js'),\n        path.join(srcDir, 'templates/renderer.ts')\n      ],\n      documents: [\n        path.join(outputDir, 'README.md'),\n        path.join(outputDir, 'docs/usage.md')\n      ]\n    };\n    \n    // Create context\n    const context = createContext();\n    Object.assign(context, sampleContext);\n    \n    // Create template renderer with the output directory as the template dir\n    const renderer = new TemplateRenderer(context, outputDir);\n    \n    // Register collections directly\n    renderer.registerCollections(collections);\n    \n    logger.info('Registered collections', {\n      collectionNames: Object.keys(collections),\n      codeFiles: collections.code,\n      docFiles: collections.documents\n    });\n    \n    // Test normal template rendering\n    const renderedTemplate = await renderer.renderTemplate('test-template.md');\n    logger.info('Rendered normal template');\n    \n    // Test LLM response rendering\n    const renderedLLMResponse = await renderer.render(SAMPLE_LLM_RESPONSE, { isLLMResponse: true });\n    logger.info('Rendered LLM response');\n    \n    // Save rendered outputs\n    const outputPath = path.join(outputDir, 'test-output.md');\n    const llmOutputPath = path.join(outputDir, 'test-llm-output.md');\n    \n    await writeFile(outputPath, renderedTemplate);\n    await writeFile(llmOutputPath, renderedLLMResponse);\n    \n    logger.info('Template test completed successfully', {\n      outputPath,\n      llmOutputPath,\n      contextKeys: Object.keys(context),\n      collections: Object.keys(collections)\n    });\n    \n    console.log(`Test completed. Output saved to: ${outputPath} and ${llmOutputPath}`);\n  } catch (error) {\n    logger.error('Template test failed', { error });\n    console.error('Test failed:', (error as Error).message);\n  }\n}\n\n// Run the test if this file is executed directly\nif (process.argv[1] === fileURLToPath(import.meta.url)) {\n  runTest();\n} ",
    "filename": "templates/test.ts"
  },
  {
    "content": "import path from 'path';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\nimport { runHumanReview } from './processors/human-review.ts';\nimport { ensureDir, writeFile } from './utils/fs.ts';\nimport logger from './utils/logger.ts';\n\n// Set up the directory path based on ES modules\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst ROOT_DIR = path.resolve(__dirname, '..');\n\n// Main function\nasync function main() {\n  try {\n    // Create test directory\n    const testDir = path.join(ROOT_DIR, '_dev/test-review');\n    await ensureDir(testDir);\n    \n    // Create a sample document to review\n    const sampleDocPath = path.join(testDir, 'sample-spec.md');\n    const sampleDoc = `# Sample Type Specification\n\n## Overview\n\nThis is a sample type specification for the \"example\" directive.\n\n## Type Definition\n\n\\`\\`\\`typescript\ninterface ExampleDirective {\n  type: 'example';\n  name: string;\n  options?: {\n    flag1?: boolean;\n    flag2?: string;\n  };\n}\n\\`\\`\\`\n\n## Properties\n\n| Property | Type | Required | Description |\n|----------|------|----------|-------------|\n| type | string | Yes | Must be \"example\" |\n| name | string | Yes | Name of the example |\n| options | object | No | Optional configuration |\n\n## Examples\n\n\\`\\`\\`typescript\nconst example: ExampleDirective = {\n  type: 'example',\n  name: 'test-example',\n  options: {\n    flag1: true,\n    flag2: 'test'\n  }\n};\n\\`\\`\\`\n`;\n    \n    await writeFile(sampleDocPath, sampleDoc);\n    logger.info(`Created sample document at ${sampleDocPath}`);\n    \n    // Create output path\n    const outputPath = path.join(testDir, 'reviewed-spec.md');\n    \n    // Run human review\n    logger.info('Running human review process...');\n    const result = await runHumanReview({\n      phase: 'test-phase',\n      promptTemplate: 'templates/human-review.md',\n      context: {\n        phase: 'Sample Review',\n        directiveName: 'example',\n        finalSpec: true\n      },\n      inputPath: sampleDocPath,\n      outputPath,\n      required: true\n    });\n    \n    logger.info('Human review process completed:', result);\n    logger.info(`Approved: ${result.approved}`);\n    \n    if (result.modifications) {\n      logger.info(`Modifications: ${result.modifications}`);\n    }\n    \n    if (result.notes) {\n      logger.info(`Notes: ${result.notes}`);\n    }\n    \n    logger.info('Test completed successfully');\n  } catch (error) {\n    logger.error('Test failed:', error);\n    process.exit(1);\n  }\n}\n\n// Run the main function\nmain().catch(error => {\n  logger.error('Unhandled error:', error);\n  process.exit(1);\n}); ",
    "filename": "test-human-review.ts"
  },
  {
    "content": "import { fileURLToPath } from 'url';\nimport { dirname } from 'path';\nimport path from 'path';\nimport { config } from 'dotenv';\nimport { logger } from './logger.js';\n\n/**\n * Load environment variables from .env file\n */\nexport function loadEnvVars() {\n  const __filename = fileURLToPath(import.meta.url);\n  const __dirname = dirname(__filename);\n  const ROOT_DIR = path.resolve(__dirname, '../..');\n\n  // Load environment variables from .env file in the root directory\n  const result = config({ path: path.join(ROOT_DIR, '.env') });\n  logger.debug('Loaded environment variables:', {\n    loaded: result.parsed !== undefined,\n    error: result.error,\n    envPath: path.join(ROOT_DIR, '.env')\n  });\n\n  return result;\n} ",
    "filename": "utils/env.ts"
  },
  {
    "content": "import fs from 'fs/promises';\nimport path from 'path';\nimport logger from './logger.ts';\n\n/**\n * Ensures a directory exists, creating it if necessary\n * @param directoryPath Path to the directory\n */\nexport async function ensureDir(directoryPath: string): Promise<void> {\n  try {\n    await fs.mkdir(directoryPath, { recursive: true });\n    logger.debug(`Directory ensured: ${directoryPath}`);\n  } catch (error) {\n    logger.error(`Failed to create directory: ${directoryPath}`, { error });\n    throw error;\n  }\n}\n\n/**\n * Reads a file and returns its contents\n * @param filePath Path to the file\n * @returns File contents as string\n */\nexport async function readFile(filePath: string): Promise<string> {\n  try {\n    const content = await fs.readFile(filePath, 'utf8');\n    logger.debug(`File read: ${filePath}`);\n    return content;\n  } catch (error) {\n    logger.error(`Failed to read file: ${filePath}`, { error });\n    throw new Error(`Unable to read file at ${filePath}: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Writes content to a file, creating directories if needed\n * @param filePath Path to the file\n * @param content Content to write\n */\nexport async function writeFile(filePath: string, content: string): Promise<void> {\n  try {\n    // Ensure the directory exists\n    await ensureDir(path.dirname(filePath));\n    \n    // Write the file\n    await fs.writeFile(filePath, content, 'utf8');\n    logger.debug(`File written: ${filePath}`);\n  } catch (error) {\n    logger.error(`Failed to write file: ${filePath}`, { error });\n    throw new Error(`Unable to write file at ${filePath}: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Checks if a file exists\n * @param filePath Path to the file\n * @returns True if file exists, false otherwise\n */\nexport async function fileExists(filePath: string): Promise<boolean> {\n  try {\n    await fs.access(filePath, fs.constants.F_OK);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Lists files in a directory matching a pattern\n * @param directoryPath Path to the directory\n * @param pattern Optional regex pattern to filter files\n * @returns Array of file paths\n */\nexport async function listFiles(directoryPath: string, pattern?: RegExp): Promise<string[]> {\n  try {\n    const files = await fs.readdir(directoryPath);\n    let filePaths = files.map(file => path.join(directoryPath, file));\n    \n    // Filter by pattern if provided\n    if (pattern) {\n      filePaths = filePaths.filter(filePath => pattern.test(filePath));\n    }\n    \n    return filePaths;\n  } catch (error) {\n    logger.error(`Failed to list files in directory: ${directoryPath}`, { error });\n    throw new Error(`Unable to list files in ${directoryPath}: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Resolves path strings with placeholders using a context object\n * @param pathTemplate Path template or object with path templates\n * @param context Context object with values for placeholders\n * @returns Resolved path string or object\n */\nexport function resolvePath(\n  pathTemplate: string | { [key: string]: any },\n  context: Record<string, any>\n): string | { [key: string]: any } {\n  // If pathTemplate is an object, recursively resolve its values\n  if (typeof pathTemplate === 'object' && pathTemplate !== null) {\n    const result: { [key: string]: any } = {};\n    for (const [key, value] of Object.entries(pathTemplate)) {\n      result[key] = resolvePath(value, context);\n    }\n    return result;\n  }\n\n  // If pathTemplate is a string, resolve placeholders\n  if (typeof pathTemplate === 'string') {\n    return pathTemplate.replace(/\\{\\{([^}]+)\\}\\}/g, (_, key) => {\n      return context[key] !== undefined ? context[key] : `{{${key}}}`;\n    });\n  }\n\n  // Return unchanged for other types\n  return pathTemplate;\n}",
    "filename": "utils/fs.ts"
  },
  {
    "content": "// Export logger\nexport * from './logger.js';\n\n// Export file system utilities\nexport * from './fs.js';\n\n// Export template utilities\nexport * from './template.js';\n\n// Export LLM XML utilities\nexport * from './llmxml.js'; ",
    "filename": "utils/index.ts"
  },
  {
    "content": "import { createLLMXML } from 'llmxml';\nimport { XMLParser } from 'fast-xml-parser';\nimport logger from './logger.js';\n\n// Create the LLMXML instance with default configuration\nconst llmxml = createLLMXML({\n  // Default threshold for fuzzy matching (0-1)\n  defaultFuzzyThreshold: 0.7,\n  \n  // Warning emission level\n  warningLevel: 'all',\n\n  // Control XML attribute output\n  includeTitle: false,\n  includeHlevel: false,\n  verbose: false,\n\n  // Tag name formatting\n  tagFormat: 'PascalCase',\n});\n\n// Register warning handler\nllmxml.onWarning((warning) => {\n  logger.warn('LLMXML warning', warning);\n});\n\n/**\n * Convert Markdown content to LLM-XML\n * @param markdown Markdown content to convert\n * @returns XML-structured content\n */\nexport async function toXML(markdown: string): Promise<string> {\n  try {\n    return await llmxml.toXML(markdown);\n  } catch (error) {\n    logger.error('Error converting Markdown to XML', { error });\n    throw new Error(`LLMXML conversion error: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Convert LLM-XML content back to Markdown\n * @param xml XML content to convert\n * @returns Markdown-structured content\n */\nexport async function toMarkdown(xml: string): Promise<string> {\n  try {\n    return await llmxml.toMarkdown(xml);\n  } catch (error) {\n    logger.error('Error converting XML to Markdown', { error });\n    throw new Error(`LLMXML conversion error: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Extract a section from Markdown content\n * @param content Markdown content\n * @param sectionName Section name to extract\n * @param options Extraction options\n * @returns Extracted section content\n */\nexport async function getSection(\n  content: string, \n  sectionName: string, \n  options?: {\n    level?: number;\n    exact?: boolean;\n    includeNested?: boolean;\n    fuzzyThreshold?: number;\n  }\n): Promise<string> {\n  try {\n    return await llmxml.getSection(content, sectionName, options);\n  } catch (error) {\n    logger.error('Error extracting section', { error, sectionName });\n    throw new Error(`Section extraction error: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Extract multiple sections from Markdown content\n * @param content Markdown content\n * @param sectionName Section name pattern to extract\n * @param options Extraction options\n * @returns Array of matching sections\n */\nexport async function getSections(\n  content: string, \n  sectionName: string, \n  options?: {\n    level?: number;\n    exact?: boolean;\n    includeNested?: boolean;\n    fuzzyThreshold?: number;\n  }\n): Promise<string[]> {\n  try {\n    return await llmxml.getSections(content, sectionName, options);\n  } catch (error) {\n    logger.error('Error extracting sections', { error, sectionName });\n    throw new Error(`Sections extraction error: ${(error as Error).message}`);\n  }\n}\n\nexport default {\n  toXML,\n  toMarkdown,\n  getSection,\n  getSections\n}; ",
    "filename": "utils/llmxml.ts"
  },
  {
    "content": "import winston from 'winston';\nimport path from 'path';\nimport fs from 'fs';\n\nconst { createLogger, format, transports } = winston;\nconst { combine, timestamp, printf, colorize } = format;\n\n/**\n * Sets up the application logger\n * @returns Winston logger instance\n */\nexport function setupLogger() {\n  // Create logs directory if it doesn't exist\n  const logsDir = path.join(process.cwd(), 'logs');\n  if (!fs.existsSync(logsDir)) {\n    fs.mkdirSync(logsDir, { recursive: true });\n  }\n\n  // Custom format for console output\n  const consoleFormat = printf(({ level, message, timestamp, ...metadata }) => {\n    let msg = `${timestamp} [${level}]: ${message}`;\n    if (Object.keys(metadata).length > 0) {\n      msg += ` ${JSON.stringify(metadata)}`;\n    }\n    return msg;\n  });\n\n  // Configure the logger\n  const logger = createLogger({\n    level: process.env.LOG_LEVEL || 'info',\n    format: combine(\n      timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),\n      winston.format.json()\n    ),\n    defaultMeta: { service: 'committee' },\n    transports: [\n      // Write logs to files\n      new transports.File({ \n        filename: path.join(logsDir, 'error.log'), \n        level: 'error' \n      }),\n      new transports.File({ \n        filename: path.join(logsDir, 'combined.log') \n      }),\n    ],\n  });\n\n  // Add console transport for non-production environments\n  if (process.env.NODE_ENV !== 'production') {\n    logger.add(new transports.Console({\n      format: combine(\n        colorize(),\n        timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),\n        consoleFormat\n      ),\n    }));\n  }\n\n  return logger;\n}\n\n// Create and export a logger instance\nexport const logger = setupLogger();\n\n// Also export as default for backward compatibility\nexport default logger; ",
    "filename": "utils/logger.ts"
  },
  {
    "content": "import path from 'path';\n\n/**\n * Components that make up a Committee path\n */\nexport interface PathComponents {\n  phase: string;\n  phaseIteration?: string;\n  set: string;\n  setIteration?: string;\n  task?: string;\n  taskIteration?: string;\n}\n\n/**\n * Utility class for handling Committee paths\n */\nexport class CommitteePaths {\n  /**\n   * Constructs a path from components\n   */\n  static constructPath(base: string, components: PathComponents): string {\n    const parts: string[] = [];\n\n    // Add phase\n    parts.push(components.phase);\n\n    // Add phase iteration if present\n    if (components.phaseIteration) {\n      parts.push(components.phaseIteration);\n    }\n\n    // Add set\n    if (components.set) {\n      parts.push(components.set);\n      \n      // Add set iteration if present\n      if (components.setIteration) {\n        parts.push(components.setIteration);\n      }\n    }\n\n    // Add task if present\n    if (components.task) {\n      parts.push(components.task);\n\n      // Add task iteration if present\n      if (components.taskIteration) {\n        parts.push(components.taskIteration);\n      }\n    }\n\n    // Filter out any empty or undefined parts\n    const filteredParts = parts.filter(part => part && part.trim());\n\n    return path.join(base, ...filteredParts);\n  }\n\n  /**\n   * Resolves a full path back into its components\n   */\n  static resolvePath(fullPath: string): PathComponents {\n    // Remove file extension if present\n    const withoutExt = fullPath.replace(/\\.[^/.]+$/, '');\n    \n    // Split path into parts\n    const parts = withoutExt.split(path.sep).filter(Boolean);\n    \n    // We expect paths to be in the format:\n    // phase/phase-iteration?/set/set-iteration?/task?/task-iteration?\n    const components: PathComponents = {\n      phase: parts[0],\n      set: parts[parts.length >= 2 ? 1 : 0]\n    };\n\n    // If we have more than 2 parts, we need to determine what they are\n    if (parts.length > 2) {\n      let currentIndex = 2;\n\n      // Check if we have a phase iteration\n      if (parts[1].startsWith(parts[0]) || !parts[1].includes('/')) {\n        components.phaseIteration = parts[1];\n        components.set = parts[2];\n        currentIndex = 3;\n      }\n\n      // Check for set iteration\n      if (currentIndex < parts.length) {\n        if (parts[currentIndex].startsWith(components.set) || !parts[currentIndex].includes('/')) {\n          components.setIteration = parts[currentIndex];\n          currentIndex++;\n        }\n      }\n\n      // Check for task and task iteration\n      if (currentIndex < parts.length) {\n        components.task = parts[parts.length - 1];\n        if (currentIndex < parts.length - 1) {\n          components.taskIteration = parts[parts.length - 2];\n        }\n      }\n    }\n\n    return components;\n  }\n\n  /**\n   * Gets a name for an iteration based on the item\n   */\n  static getIterationName(item: any, index: number, prefix: string): string {\n    // Sanitize the prefix to be filesystem safe\n    const safePrefix = prefix.replace(/[^a-zA-Z0-9-_]/g, '-');\n\n    if (typeof item === 'string') {\n      // Sanitize string values to be filesystem safe\n      return item.replace(/[^a-zA-Z0-9-_]/g, '-');\n    }\n    if (item?.name) {\n      return String(item.name).replace(/[^a-zA-Z0-9-_]/g, '-');\n    }\n    if (item?.id) {\n      return String(item.id).replace(/[^a-zA-Z0-9-_]/g, '-');\n    }\n    return `${safePrefix}-${index + 1}`;\n  }\n\n  /**\n   * Constructs an output file path\n   */\n  static constructOutputPath(base: string, components: PathComponents, type?: 'thinking' | 'response'): string {\n    const basePath = this.constructPath(base, components);\n    const filename = components.task || components.set;\n    if (type) {\n      return path.join(basePath, `${filename}-${type}.o.md`);\n    }\n    return path.join(basePath, `${filename}.o.md`);\n  }\n\n  /**\n   * Constructs a prompt file path\n   */\n  static constructPromptPath(base: string, components: PathComponents, type: 'thinking' | 'response'): string {\n    const basePath = this.constructPath(base, components);\n    const filename = components.task || components.set;\n    return path.join(basePath, `${filename}-${type}.prompt.xml`);\n  }\n} ",
    "filename": "utils/paths.ts"
  },
  {
    "content": "import Mustache from 'mustache';\nimport path from 'path';\nimport { readFile } from './fs.ts';\nimport logger from './logger.ts';\n\n/**\n * Loads and renders a template file with the given context\n * @param templatePath Path to the template file\n * @param context Object containing values for template variables\n * @returns Rendered template as string\n */\nexport async function renderTemplateFile(\n  templatePath: string, \n  context: Record<string, any>\n): Promise<string> {\n  try {\n    logger.debug(`Loading template: ${templatePath}`);\n    const template = await readFile(templatePath);\n    return renderTemplate(template, context);\n  } catch (error) {\n    logger.error(`Failed to render template file: ${templatePath}`, { error });\n    throw new Error(`Unable to render template at ${templatePath}: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Renders a template string with the given context\n * @param template Template string with Mustache tags\n * @param context Object containing values for template variables\n * @param options Optional rendering options\n * @returns Rendered template as string\n */\nexport function renderTemplate(\n  template: string, \n  context: Record<string, any>,\n  options: { \n    preserveTemplates?: string[],\n    isLLMResponse?: boolean \n  } = {}\n): string {\n  try {\n    // Disable HTML escaping as we're working with markdown\n    Mustache.escape = (text) => text;\n\n    // Split template into frontmatter and content if it exists\n    const frontmatterMatch = template.match(/^(---\\n[\\s\\S]+?\\n---\\n|{\\n[\\s\\S]+?\\n}\\n)([\\s\\S]*)$/);\n    \n    if (frontmatterMatch) {\n      const [, frontmatter, content] = frontmatterMatch;\n      \n      // Render frontmatter with raw context (no escaping)\n      const renderedFrontmatter = Mustache.render(frontmatter, context);\n      \n      // Create safe context for content rendering\n      const safeContext = createSafeContext(context, options.preserveTemplates || []);\n      \n      // If this is an LLM response, render with safe context then escape any remaining tags\n      const renderedContent = options.isLLMResponse ? \n        escapeMustacheTags(Mustache.render(content, safeContext)) : \n        Mustache.render(content, safeContext);\n      \n      logger.debug('Rendering markdown template with frontmatter', { \n        contextKeys: Object.keys(context),\n        preservedTemplates: options.preserveTemplates,\n        hasFrontmatter: true,\n        isLLMResponse: options.isLLMResponse\n      });\n      \n      return renderedFrontmatter + renderedContent;\n    }\n    \n    // No frontmatter - treat entire template as content\n    const safeContext = createSafeContext(context, options.preserveTemplates || []);\n    \n    logger.debug('Rendering template with context', { \n      contextKeys: Object.keys(context),\n      preservedTemplates: options.preserveTemplates,\n      hasFrontmatter: false,\n      isLLMResponse: options.isLLMResponse\n    });\n    \n    // If this is an LLM response, render with safe context then escape any remaining tags\n    return options.isLLMResponse ? \n      escapeMustacheTags(Mustache.render(template, safeContext)) : \n      Mustache.render(template, safeContext);\n  } catch (error) {\n    logger.error('Failed to render template', { error });\n    throw new Error(`Template rendering failed: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Creates a safe context object by escaping any mustache tags in string values\n * @param context Original context object\n * @param preserveTemplates Array of context keys whose template syntax should be preserved\n * @returns New context object with escaped values\n */\nfunction createSafeContext(\n  context: Record<string, any>,\n  preserveTemplates: string[] = []\n): Record<string, any> {\n  const safeContext: Record<string, any> = {};\n  \n  for (const [key, value] of Object.entries(context)) {\n    if (typeof value === 'string') {\n      // Don't escape Mustache tags - let them be processed normally\n      safeContext[key] = value;\n    } else if (value && typeof value === 'object') {\n      // Recursively process nested objects\n      safeContext[key] = createSafeContext(value, preserveTemplates);\n    } else {\n      // Pass through non-string values unchanged\n      safeContext[key] = value;\n    }\n  }\n  \n  return safeContext;\n}\n\n/**\n * Escapes any mustache tags in a string by adding a zero-width space between { and {\n * Only used for LLM responses where we want to preserve the tags\n * @param text Text containing mustache tags to escape\n * @returns Text with escaped mustache tags\n */\nfunction escapeMustacheTags(text: string): string {\n  // Only escape if the text appears to be an LLM response with template tags\n  if (text.includes('{{') && text.includes('}}')) {\n    return text.replace(/{{/g, '{​{').replace(/}}/g, '}​}');\n  }\n  return text;\n}\n\n/**\n * Loads a template file from the template directory\n * @param templateName Template name within templates directory\n * @returns Template content as string\n */\nexport async function loadTemplate(templateName: string): Promise<string> {\n  const templateDir = process.env.TEMPLATE_DIR || 'templates';\n  const templatePath = path.join(process.cwd(), templateDir, templateName);\n  \n  try {\n    logger.debug(`Loading template: ${templatePath}`);\n    return await readFile(templatePath);\n  } catch (error) {\n    logger.error(`Failed to load template: ${templatePath}`, { error });\n    throw new Error(`Unable to load template ${templateName}: ${(error as Error).message}`);\n  }\n}\n\n/**\n * Resolves variables in a template string that match a file collection\n * @param template Template string\n * @param collections Map of collection names to file paths\n * @param contentLoader Function to load file content\n * @returns Template with file collections expanded\n */\nexport async function resolveFileCollections(\n  template: string,\n  collections: Record<string, string[]>,\n  contentLoader: (path: string) => Promise<string>\n): Promise<string> {\n  // Find all variable references in the template\n  const variableRegex = /\\{\\{([^}:]+)(?::([^}]+))?\\}\\}/g;\n  const variables = [...template.matchAll(variableRegex)].map(match => ({\n    full: match[0],\n    name: match[1],\n    filter: match[2],\n  }));\n  \n  let resolvedTemplate = template;\n  \n  // Process each variable that might be a file collection\n  for (const variable of variables) {\n    const { name, filter } = variable;\n    \n    // Check if this variable refers to a file collection\n    if (collections[name]) {\n      logger.debug(`Resolving file collection: ${name}`);\n      \n      // Get the matching files (apply filter if any)\n      const filePaths = filter \n        ? collections[name].filter(path => path.includes(filter))\n        : collections[name];\n      \n      if (filePaths.length === 0) {\n        const replacement = `<!-- No matching files for ${name}${filter ? ': ' + filter : ''} -->`;\n        resolvedTemplate = resolvedTemplate.replace(variable.full, replacement);\n        continue;\n      }\n      \n      // Build XML content from matching files\n      const contentParts = [];\n      contentParts.push(`<${name}>`);\n      \n      for (const filePath of filePaths) {\n        try {\n          const content = await contentLoader(filePath);\n          contentParts.push(`  <${filePath}>`);\n          contentParts.push(content);\n          contentParts.push(`  </${filePath}>`);\n        } catch (error) {\n          logger.warn(`Failed to load file for collection: ${filePath}`, { error });\n          contentParts.push(`  <${filePath}>`);\n          contentParts.push(`  <!-- Error loading file: ${(error as Error).message} -->`);\n          contentParts.push(`  </${filePath}>`);\n        }\n      }\n      \n      contentParts.push(`</${name}>`);\n      const replacement = contentParts.join('\\n');\n      \n      // Replace the variable with the collection content\n      resolvedTemplate = resolvedTemplate.replace(variable.full, replacement);\n    }\n  }\n  \n  return resolvedTemplate;\n} ",
    "filename": "utils/template.ts"
  },
  {
    "content": "name: \"complex-workflow\"\ndescription: \"Multi-phase workflow with parallel processing and human review steps\"\noutputPath: \"examples-output/complex\"\nvariables:\n  directiveName: \"UserProfile\"\n  services:\n    - name: \"AuthService\"\n      description: \"Handles user authentication and authorization\"\n    - name: \"ProfileService\"\n      description: \"Manages user profile data and preferences\"\n    - name: \"NotificationService\"\n      description: \"Handles user notifications and communication preferences\"\nphases:\n  - usePhase: \"requirements-collection\"\n  - usePhase: \"draft-spec-creation\"\n    dependsOn: [\"requirements-collection\"]\n  - usePhase: \"review\"\n    dependsOn: [\"draft-spec-creation\"]\n  - usePhase: \"feedback-collection\"\n    dependsOn: [\"review\"]\n  - usePhase: \"final-spec-creation\"\n    dependsOn: [\"feedback-collection\"]\n  - usePhase: \"final-review\"\n    dependsOn: [\"final-spec-creation\"] ",
    "filename": "workflow.yaml"
  }
]