Skip to content

feat: implement company template system with 7 built-in presets#85

Merged
Aureliolo merged 3 commits intomainfrom
feat/company-templates
Mar 1, 2026
Merged

feat: implement company template system with 7 built-in presets#85
Aureliolo merged 3 commits intomainfrom
feat/company-templates

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Implements a two-pass template rendering pipeline that bootstraps company configurations from pre-built YAML templates with full Jinja2 variable substitution (SandboxedEnvironment)
  • Ships 7 built-in templates: solo_founder, startup, dev_shop, product_team, agency, full_company, research_lab
  • Adds template schema models (CompanyTemplate, TemplateVariable, TemplateAgentConfig, TemplateDepartmentConfig, TemplateMetadata) with full Pydantic v2 validation
  • Supports user template directory (~/.ai-company/templates/) with override priority over builtins
  • Extracts deep_merge() to config/utils.py as a shared public utility
  • Adds 4 minimal personality presets and deterministic auto-name generation for agents
  • Excludes Jinja2 template YAML files from check-yaml pre-commit hook

Two-Pass Pipeline

  1. Pass 1 (Loader): Strips Jinja2 expressions, YAML-parses to extract metadata + variables (plain YAML)
  2. Pass 2 (Renderer): Collects user variables + defaults, Jinja2-renders raw YAML, YAML-parses result, builds config dict, deep-merges with defaults, validates as RootConfig

Follow-Up Issues Created

Test plan

  • 88 new unit tests across 6 test files (schema, loader, renderer, presets, utils, conftest)
  • All 7 built-in templates load and render to valid RootConfig
  • Variable substitution (defaults, overrides, required vars)
  • Error cases (missing vars, invalid YAML, bad Jinja2, validation failures)
  • Agent name uniqueness and auto-generation
  • User template directory override
  • ruff check clean, mypy strict clean, 97% coverage (1048 tests total)

Closes #60

🤖 Generated with Claude Code

Add a two-pass template rendering pipeline that bootstraps company
configurations from pre-built YAML templates with Jinja2 variable
substitution. Pass 1 extracts metadata and variables from plain YAML,
Pass 2 renders the full template through a SandboxedEnvironment then
validates as RootConfig.

- Extract deep_merge to config/utils.py as shared utility
- Add template error hierarchy (TemplateError, NotFound, Render, Validation)
- Add Pydantic schema models (CompanyTemplate, TemplateVariable, etc.)
- Add 4 minimal personality presets and auto-name generation
- Add template loader with user dir (~/.ai-company/templates/) override
- Add Jinja2 renderer pipeline with variable collection and agent expansion
- Ship 7 built-in templates: solo_founder, startup, dev_shop, product_team,
  agency, full_company, research_lab
- Add jinja2==3.1.6 explicit dependency
- Exclude Jinja2 template files from check-yaml pre-commit hook
- 88 new unit tests, 97% overall coverage

Closes #60
Copilot AI review requested due to automatic review settings February 28, 2026 19:19
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 28, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ad6519 and e52bfe7.

📒 Files selected for processing (3)
  • src/ai_company/templates/loader.py
  • src/ai_company/templates/renderer.py
  • src/ai_company/templates/schema.py

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Two-pass template system with public template API, validation, and clear rendering/validation errors.
    • Seven built-in templates (Agency, Dev Shop, Full Company, Product Team, Research Lab, Solo Founder, Tech Startup).
    • Personality presets and deterministic auto-name generation; templates support variables, departments, agents, workflows, and communication.
  • Chores

    • Added Jinja2 dependency and updated YAML check exclusion for built-in templates.
  • Tests

    • Extensive unit tests for loader, renderer, schema, presets, and utilities.

Walkthrough

Adds a template subsystem: Pydantic schema, two‑pass loader, sandboxed Jinja2 renderer, personality presets/auto-naming, seven built-in YAML templates, template exceptions, a shared config deep_merge utility, Jinja2 dependency, tests, and pre-commit exclusion for builtins.

Changes

Cohort / File(s) Summary
Repo config
\.pre-commit-config.yaml, pyproject.toml
Skip YAML checks for built-in templates; add jinja2==3.1.6 dependency.
Config utils & loader
src/ai_company/config/utils.py, src/ai_company/config/loader.py, tests/unit/config/*
Add deep_merge and to_float utilities; remove local _deep_merge and replace usages; update/add tests for deep_merge; remove old _deep_merge tests.
Template package surface
src/ai_company/templates/__init__.py
Expose template public API (errors, loader, renderer, schema, presets) at package root.
Template errors
src/ai_company/templates/errors.py
Add TemplateError hierarchy: TemplateError, TemplateNotFoundError, TemplateRenderError, TemplateValidationError (captures locations and per-field errors).
Template schema
src/ai_company/templates/schema.py
Add frozen Pydantic models: TemplateVariable, TemplateAgentConfig, TemplateDepartmentConfig, TemplateMetadata, CompanyTemplate with validators (agent counts, uniqueness, types).
Loader (two-pass)
src/ai_company/templates/loader.py
Add two-pass loader: strip Jinja2 for pass1 metadata parsing, support built-in and user templates, list_*/load_* functions, LoadedTemplate/TemplateInfo, and robust error mapping.
Renderer (second pass)
src/ai_company/templates/renderer.py
Add sandboxed Jinja2 rendering, variable collection/merging, YAML parse of rendered output, build and deep-merge config dict, validate to RootConfig, expose render_template.
Presets & auto-names
src/ai_company/templates/presets.py
Add PERSONALITY_PRESETS, get_personality_preset, and generate_auto_name with optional deterministic seed.
Built-in templates
src/ai_company/templates/builtins/...
agency.yaml, dev_shop.yaml, full_company.yaml, product_team.yaml, research_lab.yaml, solo_founder.yaml, startup.yaml
Add seven YAML template definitions covering various company scenarios, variables, departments, agents, workflows, and communication modes.
Template tests
tests/unit/templates/*
conftest.py, test_loader.py, test_presets.py, test_renderer.py, test_schema.py
Comprehensive unit tests for loader, renderer, schema validation, presets, auto-naming, and error paths; new fixtures for template YAML samples.
Tests: config utils
tests/unit/config/test_utils.py
Add tests for deep_merge semantics (recursive, non-destructive, list replacement).
Tests: removed deep_merge tests
tests/unit/config/test_loader.py
Removed prior _deep_merge test suite and imports after refactor.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Loader as Loader (pass1)
    participant Source as YAML Source
    participant Renderer as Renderer (pass2)
    participant Validator as RootConfig Validator

    User->>Loader: list_templates() / load_template(name)
    Loader->>Source: fetch raw YAML (builtin or user)
    Source-->>Loader: raw YAML text
    Loader->>Loader: strip Jinja2, parse metadata (pass1)
    Loader-->>User: LoadedTemplate (raw_yaml, metadata)

    User->>Renderer: render_template(LoadedTemplate, variables)
    Renderer->>Renderer: collect variables (defaults + user)
    Renderer->>Renderer: create sandboxed Jinja2 env
    Renderer->>Renderer: render raw_yaml -> rendered YAML
    Renderer->>Loader: parse rendered YAML (pass2)
    Loader-->>Renderer: parsed config dict
    Renderer->>Renderer: expand agents, build departments, to_float, deep_merge defaults
    Renderer->>Validator: validate merged dict -> RootConfig
    Validator-->>Renderer: validated RootConfig
    Renderer-->>User: RootConfig
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.01% which is insufficient. The required threshold is 100.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature being introduced: a company template system with 7 built-in presets. It accurately summarizes the primary scope of the changeset.
Description check ✅ Passed The description comprehensively covers the implementation details, two-pass pipeline, 7 built-in templates, schema models, user template directory support, configuration refactoring, personality presets, pre-commit hook updates, and test coverage. It is directly related to the changeset.
Linked Issues check ✅ Passed The PR fulfills all acceptance criteria from issue #60: provides default configurations, defines Pydantic schema models, supplies 7 templates (exceeding the 2-template minimum), implements Jinja2 variable substitution, includes validation on load, and provides comprehensive unit test coverage (88 new tests).
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issue #60 objectives. The pre-commit config modification, config utility extraction, and dependency additions (jinja2) are necessary supporting changes for the template system implementation and are not out-of-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/company-templates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust and flexible template system for defining AI company configurations. It establishes a two-stage process for loading and rendering YAML-based templates, enabling dynamic variable substitution and ensuring structural integrity through Pydantic validation. The system comes with a set of predefined company structures and allows for user-defined overrides, significantly enhancing the configurability and reusability of company setups.

Highlights

  • Template System Implementation: Implemented a comprehensive two-pass template rendering pipeline that bootstraps company configurations from pre-built YAML templates, supporting full Jinja2 variable substitution within a SandboxedEnvironment.
  • Built-in Templates: Shipped 7 built-in company templates: solo_founder, startup, dev_shop, product_team, agency, full_company, and research_lab, providing diverse starting configurations.
  • Pydantic Schema Validation: Introduced new Pydantic v2 schema models (CompanyTemplate, TemplateVariable, TemplateAgentConfig, TemplateDepartmentConfig, TemplateMetadata) for robust validation of template structures.
  • User Template Directory Support: Added support for a user template directory (~/.ai-company/templates/) which takes override priority over built-in templates, allowing for custom configurations.
  • Shared Utility and Presets: Extracted the deep_merge() utility to a shared location and added minimal personality presets with deterministic auto-name generation for agents within templates.
  • Pre-commit Hook Exclusion: Configured the check-yaml pre-commit hook to exclude Jinja2 template YAML files from the built-in templates directory to prevent validation errors on unrendered templates.
Changelog
  • .pre-commit-config.yaml
    • Excluded built-in template YAML files from the check-yaml pre-commit hook.
  • pyproject.toml
    • Added jinja2==3.1.6 as a project dependency.
  • src/ai_company/config/loader.py
    • Removed the local _deep_merge utility function.
    • Imported the deep_merge function from the new ai_company.config.utils module.
    • Updated calls to _deep_merge to use the newly imported deep_merge.
  • src/ai_company/config/utils.py
    • Added a new file containing the deep_merge utility function for recursive dictionary merging.
  • src/ai_company/templates/init.py
    • Added a new __init__.py file to define the public API for the templates module, exposing loaders, renderers, schemas, and error types.
  • src/ai_company/templates/builtins/agency.yaml
    • Added a new built-in template for an 'Agency' company type, including variables, departments, and agents.
  • src/ai_company/templates/builtins/dev_shop.yaml
    • Added a new built-in template for a 'Dev Shop' company type, defining its structure, agents, and departments.
  • src/ai_company/templates/builtins/full_company.yaml
    • Added a new built-in template for a 'Full Company' setup, featuring a comprehensive hierarchy with multiple departments and agents.
  • src/ai_company/templates/builtins/product_team.yaml
    • Added a new built-in template for a 'Product Team', focusing on product development with design and QA roles.
  • src/ai_company/templates/builtins/research_lab.yaml
    • Added a new built-in template for a 'Research Lab', emphasizing research and data analysis roles.
  • src/ai_company/templates/builtins/solo_founder.yaml
    • Added a new built-in template for a 'Solo Founder' setup, providing a minimal configuration for quick prototypes.
  • src/ai_company/templates/builtins/startup.yaml
    • Added a new built-in template for a 'Tech Startup', designed for building MVPs with a small team.
  • src/ai_company/templates/errors.py
    • Added a new file defining a custom exception hierarchy for template-related errors, including TemplateError, TemplateNotFoundError, TemplateRenderError, and TemplateValidationError.
  • src/ai_company/templates/loader.py
    • Added a new file implementing the two-pass template loading strategy, handling built-in and user-defined templates, and stripping Jinja2 expressions for initial parsing.
  • src/ai_company/templates/presets.py
    • Added a new file providing minimal personality presets for agents and a function for deterministic auto-name generation based on roles.
  • src/ai_company/templates/renderer.py
    • Added a new file implementing the second pass of template rendering, which involves Jinja2 substitution, variable collection, and validation against the RootConfig schema.
  • src/ai_company/templates/schema.py
    • Added a new file defining Pydantic models for the company template schema, including TemplateVariable, TemplateAgentConfig, TemplateDepartmentConfig, TemplateMetadata, and CompanyTemplate.
  • tests/unit/config/test_loader.py
    • Updated imports to reflect the relocation of deep_merge.
    • Modified TestDeepMerge class to use the deep_merge utility from ai_company.config.utils.
  • tests/unit/config/test_utils.py
    • Added a new test file for the deep_merge utility function, covering various merge scenarios.
  • tests/unit/templates/conftest.py
    • Added a new file containing fixtures and constants to support unit testing of the template system.
  • tests/unit/templates/test_loader.py
    • Added a new test file to verify the functionality of template loading from built-in and file-system sources.
  • tests/unit/templates/test_presets.py
    • Added a new test file for the personality presets and auto-name generation features.
  • tests/unit/templates/test_renderer.py
    • Added a new test file to test the two-pass template rendering pipeline, including variable application and validation.
  • tests/unit/templates/test_schema.py
    • Added a new test file to validate the Pydantic models defined for the template schema.
  • uv.lock
    • Updated the lock file to include the new jinja2 dependency.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive company template system, featuring a two-pass Jinja2 rendering pipeline, Pydantic schema validation, and extensive testing. However, it contains several critical vulnerabilities related to file handling, including path traversal and arbitrary file reads in the template loading logic. A Python 2-style exception clause also causes a Denial of Service in Python 3 environments. Additionally, a critical syntax error prevents the code from running, and there are opportunities to make exception handling more specific and robust, and to fix a potential bug when scanning for user templates.


# Try user directory first.
if _USER_TEMPLATES_DIR.is_dir():
user_path = _USER_TEMPLATES_DIR / f"{name_clean}.yaml"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The load_template function is vulnerable to path traversal. It constructs a file path by joining a base directory with a user-supplied name without sanitization. An attacker could use absolute paths or parent directory references (e.g., ../../) to escape the intended directory and read arbitrary .yaml files on the system.

Recommendation: Sanitize the name input using Path(name).name to ensure only the filename is used, or verify that the resolved path is contained within _USER_TEMPLATES_DIR.

Suggested change
user_path = _USER_TEMPLATES_DIR / f"{name_clean}.yaml"
user_path = _USER_TEMPLATES_DIR / f"{Path(name_clean).name}.yaml"

Comment on lines +178 to +185
path = Path(path)
if not path.is_file():
msg = f"Template file not found: {path}"
raise TemplateNotFoundError(
msg,
locations=(ConfigLocation(file_path=str(path)),),
)
return _load_from_file(path)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The load_template_file function allows reading arbitrary files from the filesystem. If the path argument is controlled by an untrusted user, it can be used to read sensitive system files that the application has access to.

Recommendation: Implement strict validation to ensure the provided path is within an allowed directory or matches a whitelist of permitted files.


# User templates (higher priority).
if _USER_TEMPLATES_DIR.is_dir():
for path in sorted(_USER_TEMPLATES_DIR.glob("*.yaml")):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The glob("*.yaml") pattern can match directories if their names end with .yaml. The code doesn't check if path is a file before attempting to read it in _load_from_file, which will cause an IsADirectoryError. You should add a check to ensure you only process files.

Suggested change
for path in sorted(_USER_TEMPLATES_DIR.glob("*.yaml")):
for path in sorted(p for p in _USER_TEMPLATES_DIR.glob("*.yaml") if p.is_file()):

Comment on lines +103 to +104
except Exception:
logger.warning("Skipping invalid user template: %s", path)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using a broad except Exception: can mask underlying issues and makes debugging harder. It's better to catch only the specific exceptions you expect from template loading, such as TemplateRenderError and TemplateValidationError. Also, including the exception message in the log will provide more context about why a user template was skipped.

Suggested change
except Exception:
logger.warning("Skipping invalid user template: %s", path)
except (TemplateRenderError, TemplateValidationError) as e:
logger.warning("Skipping invalid user template %s: %s", path, e)

Comment on lines +118 to +119
except Exception:
logger.warning("Skipping invalid builtin template: %s", name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using a broad except Exception: can hide unexpected errors. For built-in templates, an error should be more visible than a warning, as it indicates a bug in the application itself. I suggest catching specific expected exceptions and logging them as errors to make them more prominent. This will improve debuggability without crashing the application if list_templates is called in a context where a crash is undesirable.

Suggested change
except Exception:
logger.warning("Skipping invalid builtin template: %s", name)
except (TemplateRenderError, TemplateValidationError) as e:
logger.error("Skipping invalid builtin template %r: %s", name, e)

"""
try:
return float(value)
except TypeError, ValueError:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

This line uses Python 2-style exception handling syntax (except TypeError, ValueError:), which is a SyntaxError in Python 3 and will cause a Denial of Service. To fix this, group the exceptions in a tuple for Python 3 compatibility.

Suggested change
except TypeError, ValueError:
except (TypeError, ValueError):

Comment on lines +293 to +298
except Exception as exc:
msg = f"Template validation failed for {source_name}: {exc}"
raise TemplateValidationError(
msg,
locations=(ConfigLocation(file_path=source_name),),
) from exc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Catching a broad Exception can hide unexpected errors during Pydantic model instantiation. It's better to catch the specific ValidationError that Pydantic raises. This makes the error handling more precise and robust.

You will need to add from pydantic import ValidationError to the imports at the top of the file.

Suggested change
except Exception as exc:
msg = f"Template validation failed for {source_name}: {exc}"
raise TemplateValidationError(
msg,
locations=(ConfigLocation(file_path=source_name),),
) from exc
except ValidationError as exc:
msg = f"Template validation failed for {source_name}: {exc}"
raise TemplateValidationError(
msg,
locations=(ConfigLocation(file_path=source_name),),
) from exc

Comment on lines +166 to +171
except Exception as exc:
msg = f"Jinja2 rendering failed for {source_name}: {exc}"
raise TemplateRenderError(
msg,
locations=(ConfigLocation(file_path=source_name),),
) from exc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Catching a broad Exception here can hide non-Jinja2 related bugs. It's better to be more specific and catch jinja2.TemplateError, which is the base exception for errors during Jinja2 processing. This makes the error handling more robust.

To avoid name collisions with the local TemplateError, you can import it with an alias, like from jinja2.exceptions import TemplateError as JinjaTemplateError.

Suggested change
except Exception as exc:
msg = f"Jinja2 rendering failed for {source_name}: {exc}"
raise TemplateRenderError(
msg,
locations=(ConfigLocation(file_path=source_name),),
) from exc
except jinja2.TemplateError as exc:
msg = f"Jinja2 rendering failed for {source_name}: {exc}"
raise TemplateRenderError(
msg,
locations=(ConfigLocation(file_path=source_name),),
) from exc

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ai_company/templates/builtins/dev_shop.yaml`:
- Line 21: The YAML lint error is caused by spacing inside the Jinja braces for
the budget_monthly value; update the Jinja expression associated with the
budget_monthly key (the template "{{ budget | default(75.0) }}") to remove the
extra space immediately after "{{" and before "}}" so it conforms to the braces
rule (e.g., use the same expression without those inner-leading/trailing
spaces), then re-run the YAML linter to confirm the file passes.

In `@src/ai_company/templates/builtins/full_company.yaml`:
- Line 21: The Jinja expression used for budget_monthly violates the YAML lint
"braces" rule; update the template expression for budget_monthly (the Jinja
token containing budget | default(200.0)) to remove the extra spaces directly
inside the curly braces so the braces are tight (adjust the Jinja tag around
budget | default(200.0) accordingly) to satisfy yaml linting.

In `@src/ai_company/templates/builtins/product_team.yaml`:
- Line 21: The Jinja expression on the budget_monthly line has extra spaces
inside the braces which triggers YAML linting; update the template expression in
product_team.yaml (the budget_monthly: line containing the {{ ... }} expression)
to remove the spaces immediately after '{{' and before '}}' so the expression
reads with no inner brace padding (e.g., change the existing {{ budget |
default(80.0) }} form to use no spaces adjacent to the braces).

In `@src/ai_company/templates/builtins/solo_founder.yaml`:
- Line 21: The Jinja expression for the budget in the template key
budget_monthly uses spacing that triggers YAML linting; update the Jinja braces
to remove inner spaces so the line reads with tightly wrapped braces (e.g.,
change the Jinja expression used in budget_monthly to {{budget |
default(25.0)}}) and commit the change so check-yaml passes.

In `@src/ai_company/templates/builtins/startup.yaml`:
- Line 21: The YAML lint error is caused by spacing inside the Jinja braces for
the budget_monthly key; update the Jinja expression from "{{ budget |
default(50.0) }}" to remove the inner brace spacing so it reads "{{budget |
default(50.0)}}" (i.e., edit the budget_monthly line replacing the existing
Jinja tag with {{budget | default(50.0)}} to satisfy the 'braces' rule).

In `@src/ai_company/templates/loader.py`:
- Around line 289-293: Move the call to _normalize_template_data and the shape
check for template_data into the validation try/except so malformed payloads
raise a wrapped TemplateValidationError instead of raw AttributeError/TypeError:
inside the block that constructs CompanyTemplate(**...), first assert
template_data is a mapping (e.g. isinstance(template_data, Mapping) or dict)
and/or check required keys, then call _normalize_template_data(template_data)
and pass the result to CompanyTemplate; catch exceptions and re-raise as
TemplateValidationError with the original exception info so errors from
normalization or instantiation are consistently wrapped.
- Around line 335-349: The helper _to_float currently swallows malformed numeric
strings by returning 0.0; change it to surface errors instead: in the except
ValueError branch raise a ValueError (including the original value and the
original exception message) so invalid YAML numeric input fails validation, keep
the final return float(value) for non-strings, and ensure any callers of
_to_float (where it’s used to coerce template fields) either validate input
beforehand or handle the propagated ValueError.

In `@src/ai_company/templates/renderer.py`:
- Around line 199-207: The code assumes data["template"] is a dict and returns
it directly in renderer.py; add a type check after extracting result (e.g.,
result = data["template"]) to verify it's a mapping/dict and, if not, raise
TemplateRenderError with a clear message (include source_name and the same
locations tuple using ConfigLocation) so non-mapping render outputs
(scalars/lists) produce a TemplateRenderError instead of later AttributeError;
reference the existing TemplateRenderError, ConfigLocation, data, source_name,
and result symbols when locating where to add this validation.
- Around line 380-392: The _safe_float function uses invalid exception syntax
and masks conversion errors; update it to catch exceptions with Python 3 syntax
(e.g., except (TypeError, ValueError) as e:) and stop silently returning
0.0—instead either re-raise a ValueError with a clear message including the
offending value (e.g., raise ValueError(f"Invalid numeric value for config:
{value!r}") from e) or let the original exception propagate so callers can
handle validation at system boundaries; modify the _safe_float function to
implement one of these behaviors and keep the float(value) attempt intact.

In `@tests/unit/config/test_utils.py`:
- Around line 8-68: There are duplicate TestDeepMerge tests for deep_merge;
consolidate by keeping this canonical suite (class TestDeepMerge with its test
methods) in tests/unit/config/test_utils.py and remove the overlapping tests in
tests/unit/config/test_loader.py; ensure any required import of deep_merge is
present in test_utils.py, that pytest markers (pytest.mark.unit) remain, and
that you delete or disable the duplicate TestDeepMerge class/methods in
test_loader.py so there’s a single authoritative test location.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f53832 and 4c4f3a0.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (26)
  • .pre-commit-config.yaml
  • pyproject.toml
  • src/ai_company/config/loader.py
  • src/ai_company/config/utils.py
  • src/ai_company/templates/__init__.py
  • src/ai_company/templates/builtins/__init__.py
  • src/ai_company/templates/builtins/agency.yaml
  • src/ai_company/templates/builtins/dev_shop.yaml
  • src/ai_company/templates/builtins/full_company.yaml
  • src/ai_company/templates/builtins/product_team.yaml
  • src/ai_company/templates/builtins/research_lab.yaml
  • src/ai_company/templates/builtins/solo_founder.yaml
  • src/ai_company/templates/builtins/startup.yaml
  • src/ai_company/templates/errors.py
  • src/ai_company/templates/loader.py
  • src/ai_company/templates/presets.py
  • src/ai_company/templates/renderer.py
  • src/ai_company/templates/schema.py
  • tests/unit/config/test_loader.py
  • tests/unit/config/test_utils.py
  • tests/unit/templates/__init__.py
  • tests/unit/templates/conftest.py
  • tests/unit/templates/test_loader.py
  • tests/unit/templates/test_presets.py
  • tests/unit/templates/test_renderer.py
  • tests/unit/templates/test_schema.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (5)
pyproject.toml

📄 CodeRabbit inference engine (CLAUDE.md)

Pin all dependency versions using '==' in pyproject.toml; organize into 'test' and 'dev' dependency groups

Files:

  • pyproject.toml
**/*.{yaml,yml,json,toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files

Files:

  • pyproject.toml
  • src/ai_company/templates/builtins/dev_shop.yaml
  • src/ai_company/templates/builtins/full_company.yaml
  • src/ai_company/templates/builtins/solo_founder.yaml
  • src/ai_company/templates/builtins/product_team.yaml
  • src/ai_company/templates/builtins/agency.yaml
  • src/ai_company/templates/builtins/startup.yaml
  • src/ai_company/templates/builtins/research_lab.yaml
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use PEP 649 native lazy annotations instead of from __future__ import annotations (Python 3.14+)
All public functions and classes must include type hints; enforce via mypy strict mode
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Keep functions under 50 lines and files under 800 lines
Create new objects instead of mutating existing ones; enforce immutability patterns
Use Pydantic v2 models (BaseModel, model_validator, ConfigDict) for data validation
Validate at system boundaries (user input, external APIs, config files); never silently swallow errors
Enforce 88-character line length (via ruff configuration)

Files:

  • tests/unit/templates/test_schema.py
  • src/ai_company/config/utils.py
  • tests/unit/config/test_loader.py
  • src/ai_company/templates/renderer.py
  • src/ai_company/templates/schema.py
  • tests/unit/templates/test_renderer.py
  • src/ai_company/templates/errors.py
  • tests/unit/config/test_utils.py
  • src/ai_company/templates/presets.py
  • tests/unit/templates/test_loader.py
  • src/ai_company/config/loader.py
  • tests/unit/templates/test_presets.py
  • src/ai_company/templates/__init__.py
  • tests/unit/templates/conftest.py
  • src/ai_company/templates/loader.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.py: Mark tests with @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, or @pytest.mark.slow
Maintain minimum 80% code coverage, enforced in CI via pytest-cov
Set test timeout to 30 seconds per test; use asyncio_mode = 'auto' for async tests without manual @pytest.mark.asyncio

Files:

  • tests/unit/templates/test_schema.py
  • tests/unit/config/test_loader.py
  • tests/unit/templates/test_renderer.py
  • tests/unit/config/test_utils.py
  • tests/unit/templates/test_loader.py
  • tests/unit/templates/test_presets.py
  • tests/unit/templates/conftest.py
{src/**/*.py,tests/**/*.py}

📄 CodeRabbit inference engine (CLAUDE.md)

Run ruff check and ruff format as pre-commit hooks on all Python files; enforce no commits to main branch

Files:

  • tests/unit/templates/test_schema.py
  • src/ai_company/config/utils.py
  • tests/unit/config/test_loader.py
  • src/ai_company/templates/renderer.py
  • src/ai_company/templates/schema.py
  • tests/unit/templates/test_renderer.py
  • src/ai_company/templates/errors.py
  • tests/unit/config/test_utils.py
  • src/ai_company/templates/presets.py
  • tests/unit/templates/test_loader.py
  • src/ai_company/config/loader.py
  • tests/unit/templates/test_presets.py
  • src/ai_company/templates/__init__.py
  • tests/unit/templates/conftest.py
  • src/ai_company/templates/loader.py
🧠 Learnings (11)
📚 Learning: 2026-01-26T08:59:32.818Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-26T08:59:32.818Z
Learning: Applies to pyproject.toml : Minimize external dependencies. When adding new dependencies, add them to `pyproject.toml` with exact version pins.

Applied to files:

  • pyproject.toml
📚 Learning: 2026-02-28T13:41:23.377Z
Learnt from: CR
Repo: Aureliolo/ai-company PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-28T13:41:23.377Z
Learning: Framework for orchestrating autonomous AI agents within a virtual company structure; use FastAPI for REST/WebSocket APIs

Applied to files:

  • src/ai_company/templates/builtins/full_company.yaml
  • src/ai_company/templates/builtins/startup.yaml
📚 Learning: 2026-01-24T09:54:45.426Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/instructions/agents.instructions.md:0-0
Timestamp: 2026-01-24T09:54:45.426Z
Learning: Applies to agents/test*.py : Agent tests should cover: successful generation with valid output, handling malformed LLM responses, error conditions (network errors, timeouts), output format validation, and integration with story state

Applied to files:

  • tests/unit/templates/test_schema.py
📚 Learning: 2026-01-24T09:54:45.426Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/instructions/agents.instructions.md:0-0
Timestamp: 2026-01-24T09:54:45.426Z
Learning: Applies to agents/*.py : Validate all LLM outputs by checking required fields, verifying data types, handling malformed responses, and using Pydantic models for structured data

Applied to files:

  • tests/unit/templates/test_schema.py
📚 Learning: 2026-02-28T13:41:23.377Z
Learnt from: CR
Repo: Aureliolo/ai-company PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-28T13:41:23.377Z
Learning: Applies to **/*.{yaml,yml,json,toml} : Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files

Applied to files:

  • .pre-commit-config.yaml
📚 Learning: 2026-02-26T17:43:50.902Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-26T17:43:50.902Z
Learning: Applies to .pre-commit-config.yaml : For pre-commit tools that should check entire directories, use `pass_filenames: false` instead of relying on staged file filtering.

Applied to files:

  • .pre-commit-config.yaml
📚 Learning: 2026-01-24T09:54:56.100Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/instructions/test-files.instructions.md:0-0
Timestamp: 2026-01-24T09:54:56.100Z
Learning: Each test should be independent and not rely on other tests; use pytest fixtures for test setup (shared fixtures in `tests/conftest.py`); clean up resources in teardown/fixtures

Applied to files:

  • tests/unit/config/test_utils.py
  • tests/unit/templates/conftest.py
📚 Learning: 2026-01-24T16:33:29.354Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-24T16:33:29.354Z
Learning: Applies to tests/unit/test_*.py : Write unit tests for new functionality using pytest in `tests/unit/` with `test_*.py` naming convention

Applied to files:

  • tests/unit/templates/test_presets.py
  • tests/unit/templates/conftest.py
📚 Learning: 2026-01-26T08:59:32.818Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-26T08:59:32.818Z
Learning: Applies to tests/unit/test_*.py : Write unit tests for new functionality using pytest. Place test files in `tests/unit/` with `test_*.py` naming convention.

Applied to files:

  • tests/unit/templates/conftest.py
📚 Learning: 2026-01-24T09:54:56.100Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/instructions/test-files.instructions.md:0-0
Timestamp: 2026-01-24T09:54:56.100Z
Learning: Applies to **/tests/conftest.py : Place shared pytest fixtures in `tests/conftest.py`

Applied to files:

  • tests/unit/templates/conftest.py
📚 Learning: 2026-01-26T08:59:32.818Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-26T08:59:32.818Z
Learning: Applies to tests/**/*.py : Use pytest fixtures for test setup. Shared fixtures should be in `tests/conftest.py`

Applied to files:

  • tests/unit/templates/conftest.py
🧬 Code graph analysis (10)
tests/unit/templates/test_schema.py (2)
src/ai_company/core/enums.py (2)
  • CompanyType (72-82)
  • SeniorityLevel (6-21)
src/ai_company/templates/schema.py (5)
  • CompanyTemplate (144-237)
  • TemplateAgentConfig (47-79)
  • TemplateDepartmentConfig (82-107)
  • TemplateMetadata (110-141)
  • TemplateVariable (12-44)
tests/unit/config/test_loader.py (1)
tests/unit/config/test_utils.py (1)
  • TestDeepMerge (9-68)
src/ai_company/templates/schema.py (1)
src/ai_company/core/enums.py (2)
  • CompanyType (72-82)
  • SeniorityLevel (6-21)
tests/unit/templates/test_renderer.py (4)
src/ai_company/config/schema.py (1)
  • RootConfig (199-323)
src/ai_company/templates/errors.py (1)
  • TemplateRenderError (14-15)
src/ai_company/templates/loader.py (2)
  • load_template (133-162)
  • load_template_file (165-185)
src/ai_company/templates/renderer.py (1)
  • render_template (38-84)
src/ai_company/templates/errors.py (1)
src/ai_company/config/errors.py (2)
  • ConfigError (24-59)
  • ConfigLocation (7-21)
tests/unit/config/test_utils.py (1)
tests/unit/config/test_loader.py (7)
  • TestDeepMerge (41-99)
  • test_empty_base (93-95)
  • test_empty_override (97-99)
  • test_simple_override (42-46)
  • test_nested_merge (48-52)
  • test_deeply_nested (81-85)
  • test_deeply_nested (485-489)
tests/unit/templates/test_loader.py (2)
src/ai_company/templates/errors.py (3)
  • TemplateNotFoundError (10-11)
  • TemplateRenderError (14-15)
  • TemplateValidationError (18-33)
src/ai_company/templates/loader.py (6)
  • LoadedTemplate (65-76)
  • TemplateInfo (48-61)
  • list_builtin_templates (124-130)
  • list_templates (79-121)
  • load_template (133-162)
  • load_template_file (165-185)
tests/unit/templates/test_presets.py (1)
src/ai_company/templates/presets.py (2)
  • generate_auto_name (87-103)
  • get_personality_preset (67-84)
src/ai_company/templates/__init__.py (4)
src/ai_company/templates/errors.py (4)
  • TemplateError (6-7)
  • TemplateNotFoundError (10-11)
  • TemplateRenderError (14-15)
  • TemplateValidationError (18-33)
src/ai_company/templates/loader.py (6)
  • LoadedTemplate (65-76)
  • TemplateInfo (48-61)
  • list_builtin_templates (124-130)
  • list_templates (79-121)
  • load_template (133-162)
  • load_template_file (165-185)
src/ai_company/templates/renderer.py (1)
  • render_template (38-84)
src/ai_company/templates/schema.py (5)
  • CompanyTemplate (144-237)
  • TemplateAgentConfig (47-79)
  • TemplateDepartmentConfig (82-107)
  • TemplateMetadata (110-141)
  • TemplateVariable (12-44)
src/ai_company/templates/loader.py (3)
src/ai_company/config/errors.py (1)
  • ConfigLocation (7-21)
src/ai_company/templates/errors.py (3)
  • TemplateNotFoundError (10-11)
  • TemplateRenderError (14-15)
  • TemplateValidationError (18-33)
src/ai_company/templates/schema.py (1)
  • CompanyTemplate (144-237)
🪛 YAMLlint (1.38.0)
src/ai_company/templates/builtins/dev_shop.yaml

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

src/ai_company/templates/builtins/full_company.yaml

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

src/ai_company/templates/builtins/solo_founder.yaml

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

src/ai_company/templates/builtins/product_team.yaml

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

src/ai_company/templates/builtins/agency.yaml

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

src/ai_company/templates/builtins/startup.yaml

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

src/ai_company/templates/builtins/research_lab.yaml

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

🔇 Additional comments (16)
.pre-commit-config.yaml (1)

17-17: No changes needed. The exclusion of built-in templates from check-yaml is justified because these files contain Jinja2 template syntax (e.g., {{ budget | default(100.0) }}), which would cause false positives with static YAML validation. Template validation is properly handled at runtime through the template loader and renderer system (see src/ai_company/templates/loader.py and renderer.py), which validates templates against the RootConfig schema.

Likely an incorrect or invalid review comment.

src/ai_company/templates/builtins/agency.yaml (1)

1-52: LGTM!

The template structure is well-defined with proper metadata, variables, departments, and agents. The budget percentages correctly sum to 100%. The YAMLlint warnings about brace spacing on line 21 are false positives—{{ budget | default(100.0) }} follows standard Jinja2 formatting conventions with spaces inside braces and around the pipe operator.

src/ai_company/config/utils.py (1)

7-31: LGTM!

The implementation correctly ensures immutability by deep-copying both inputs. The recursive merge logic properly handles nested dicts while replacing other types. The docstring accurately describes the behavior.

tests/unit/templates/conftest.py (1)

1-131: LGTM!

Well-structured test fixtures with clear YAML template constants covering minimal, variable-based, required-variable, invalid syntax, and missing key scenarios. The factory fixtures follow pytest best practices and enable isolated test setup.

src/ai_company/templates/presets.py (1)

1-103: LGTM!

The implementation is well-designed with proper error messages, case-insensitive lookups, and deterministic name generation via optional seeding. The shallow copy in get_personality_preset is sufficient since all nested values (tuples, strings) are immutable.

src/ai_company/templates/errors.py (1)

1-33: LGTM!

Clean exception hierarchy that properly extends the existing ConfigError framework. TemplateValidationError appropriately captures per-field errors as an immutable tuple structure for detailed validation feedback.

tests/unit/templates/test_presets.py (1)

1-74: LGTM!

Comprehensive test coverage for both get_personality_preset and generate_auto_name functions. Tests properly verify case insensitivity, whitespace handling, copy semantics, error conditions, and deterministic behavior with seeds. All tests are correctly marked with @pytest.mark.unit.

pyproject.toml (1)

16-16: jinja2==3.1.6 is valid and recommended. This is the latest version and includes patches for CVE-2025-27516 (sandbox breakout via |attr filter), CVE-2024-56201, CVE-2024-34064, and CVE-2024-22195. The version pin correctly follows the coding guidelines with exact pinning using '=='.

src/ai_company/config/loader.py (1)

20-20: Shared deep_merge integration looks correct.

The refactor keeps merge order and validation flow intact while centralizing merge behavior in one utility.

Also applies to: 422-427, 467-467

tests/unit/templates/test_schema.py (1)

18-255: Strong schema validation coverage in this test suite.

The added cases exercise core constraints (bounds, duplicates, enum validation, and frozen-model behavior) well.

tests/unit/config/test_loader.py (1)

24-24: deep_merge test migration is consistent and complete.

All merge-behavior assertions were correctly updated to the shared utility import without changing intended coverage.

Also applies to: 37-99

src/ai_company/templates/builtins/research_lab.yaml (1)

1-50: Template content is consistent with the new template pipeline.

Metadata, variables, departments, and agent definitions are coherent and align with expected built-in template shape.

src/ai_company/templates/__init__.py (1)

24-64: Public re-export surface looks clean and complete.

The package API exposes the expected loader/renderer/schema/error symbols consistently.

tests/unit/templates/test_loader.py (1)

37-201: Loader test coverage is robust for both success and failure paths.

The suite exercises precedence, path handling, and error types well.

tests/unit/templates/test_renderer.py (1)

22-191: Renderer tests cover key pipeline invariants effectively.

Good coverage across defaults, overrides, required-variable failures, and invalid Jinja2 behavior.

src/ai_company/templates/schema.py (1)

12-237: Schema model design is strong and consistent with Pydantic v2 patterns.

Immutability, field constraints, and post-validators for range/uniqueness checks are well implemented.


company:
type: "dev_shop"
budget_monthly: {{ budget | default(75.0) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix Jinja braces spacing on Line 21 to satisfy YAML linting.

budget_monthly: {{ budget | default(75.0) }} is flagged by YAMLlint (braces).

Suggested fix
-    budget_monthly: {{ budget | default(75.0) }}
+    budget_monthly: {{budget | default(75.0)}}

As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
budget_monthly: {{ budget | default(75.0) }}
budget_monthly: {{budget | default(75.0)}}
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/builtins/dev_shop.yaml` at line 21, The YAML lint
error is caused by spacing inside the Jinja braces for the budget_monthly value;
update the Jinja expression associated with the budget_monthly key (the template
"{{ budget | default(75.0) }}") to remove the extra space immediately after "{{"
and before "}}" so it conforms to the braces rule (e.g., use the same expression
without those inner-leading/trailing spaces), then re-run the YAML linter to
confirm the file passes.


company:
type: "full_company"
budget_monthly: {{ budget | default(200.0) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix Jinja braces spacing on Line 21 to satisfy YAML linting.

budget_monthly: {{ budget | default(200.0) }} is flagged by YAMLlint (braces).

Suggested fix
-    budget_monthly: {{ budget | default(200.0) }}
+    budget_monthly: {{budget | default(200.0)}}

As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
budget_monthly: {{ budget | default(200.0) }}
budget_monthly: {{budget | default(200.0)}}
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/builtins/full_company.yaml` at line 21, The Jinja
expression used for budget_monthly violates the YAML lint "braces" rule; update
the template expression for budget_monthly (the Jinja token containing budget |
default(200.0)) to remove the extra spaces directly inside the curly braces so
the braces are tight (adjust the Jinja tag around budget | default(200.0)
accordingly) to satisfy yaml linting.


company:
type: "product_team"
budget_monthly: {{ budget | default(80.0) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix Jinja braces spacing on Line 21 to satisfy YAML linting.

budget_monthly: {{ budget | default(80.0) }} is flagged by YAMLlint (braces).

Suggested fix
-    budget_monthly: {{ budget | default(80.0) }}
+    budget_monthly: {{budget | default(80.0)}}

As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
budget_monthly: {{ budget | default(80.0) }}
budget_monthly: {{budget | default(80.0)}}
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/builtins/product_team.yaml` at line 21, The Jinja
expression on the budget_monthly line has extra spaces inside the braces which
triggers YAML linting; update the template expression in product_team.yaml (the
budget_monthly: line containing the {{ ... }} expression) to remove the spaces
immediately after '{{' and before '}}' so the expression reads with no inner
brace padding (e.g., change the existing {{ budget | default(80.0) }} form to
use no spaces adjacent to the braces).


company:
type: "solo_founder"
budget_monthly: {{ budget | default(25.0) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix Jinja braces spacing on Line 21 to satisfy YAML linting.

budget_monthly: {{ budget | default(25.0) }} is flagged by YAMLlint (braces).

Suggested fix
-    budget_monthly: {{ budget | default(25.0) }}
+    budget_monthly: {{budget | default(25.0)}}

As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
budget_monthly: {{ budget | default(25.0) }}
budget_monthly: {{budget | default(25.0)}}
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/builtins/solo_founder.yaml` at line 21, The Jinja
expression for the budget in the template key budget_monthly uses spacing that
triggers YAML linting; update the Jinja braces to remove inner spaces so the
line reads with tightly wrapped braces (e.g., change the Jinja expression used
in budget_monthly to {{budget | default(25.0)}}) and commit the change so
check-yaml passes.


company:
type: "startup"
budget_monthly: {{ budget | default(50.0) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix Jinja braces spacing on Line 21 to satisfy YAML linting.

budget_monthly: {{ budget | default(50.0) }} is flagged by YAMLlint (braces).

Suggested fix
-    budget_monthly: {{ budget | default(50.0) }}
+    budget_monthly: {{budget | default(50.0)}}

As per coding guidelines **/*.{yaml,yml,json,toml}: Use check-yaml, check-json, and check-toml pre-commit hooks to validate configuration files.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
budget_monthly: {{ budget | default(50.0) }}
budget_monthly: {{budget | default(50.0)}}
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 21-21: too many spaces inside braces

(braces)


[error] 21-21: too many spaces inside braces

(braces)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/builtins/startup.yaml` at line 21, The YAML lint
error is caused by spacing inside the Jinja braces for the budget_monthly key;
update the Jinja expression from "{{ budget | default(50.0) }}" to remove the
inner brace spacing so it reads "{{budget | default(50.0)}}" (i.e., edit the
budget_monthly line replacing the existing Jinja tag with {{budget |
default(50.0)}} to satisfy the 'braces' rule).

Comment on lines +335 to +349
def _to_float(value: Any) -> float:
"""Coerce a value to float, handling string numerics.

Args:
value: Raw value from YAML (may be str, int, float).

Returns:
Float value.
"""
if isinstance(value, str):
try:
return float(value)
except ValueError:
return 0.0
return float(value)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid silent 0.0 fallback in _to_float.

Line 348 converts invalid numeric input to 0.0, which hides malformed template data instead of failing validation.

Proposed fix
 def _to_float(value: Any) -> float:
@@
-    if isinstance(value, str):
-        try:
-            return float(value)
-        except ValueError:
-            return 0.0
-    return float(value)
+    try:
+        return float(value)
+    except (TypeError, ValueError) as exc:
+        raise ValueError(f"Invalid numeric value: {value!r}") from exc

As per coding guidelines, "Validate at system boundaries (user input, external APIs, config files); never silently swallow errors".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/loader.py` around lines 335 - 349, The helper
_to_float currently swallows malformed numeric strings by returning 0.0; change
it to surface errors instead: in the except ValueError branch raise a ValueError
(including the original value and the original exception message) so invalid
YAML numeric input fails validation, keep the final return float(value) for
non-strings, and ensure any callers of _to_float (where it’s used to coerce
template fields) either validate input beforehand or handle the propagated
ValueError.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a new “company templates” subsystem that can bootstrap a RootConfig from built-in (and user-provided) YAML templates via a two-pass YAML parse + Jinja2 render pipeline.

Changes:

  • Adds template loading/rendering pipeline (ai_company.templates.*) plus 7 built-in YAML presets.
  • Extracts deep_merge() into a shared ai_company.config.utils module and updates config loader/tests accordingly.
  • Adds unit test coverage for template schema/loader/renderer and personality preset + auto-naming helpers.

Reviewed changes

Copilot reviewed 24 out of 27 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
uv.lock Adds Jinja2 dependency to the lockfile.
pyproject.toml Adds jinja2==3.1.6 as a runtime dependency.
.pre-commit-config.yaml Excludes built-in template YAML directory from check-yaml hook.
src/ai_company/config/loader.py Switches from private _deep_merge to shared deep_merge.
src/ai_company/config/utils.py Introduces shared deep_merge() utility.
src/ai_company/templates/init.py Exposes templates public API surface.
src/ai_company/templates/errors.py Adds template-specific error hierarchy.
src/ai_company/templates/loader.py Implements template discovery (builtin/user/file) + pass-1 parsing.
src/ai_company/templates/presets.py Adds minimal personality presets + deterministic auto-name generation.
src/ai_company/templates/renderer.py Implements pass-2 Jinja2 render → YAML parse → RootConfig build/validate.
src/ai_company/templates/schema.py Adds Pydantic v2 models for template schema validation.
src/ai_company/templates/builtins/init.py Marks built-in templates directory as a Python package for resources loading.
src/ai_company/templates/builtins/solo_founder.yaml Adds built-in solo founder template.
src/ai_company/templates/builtins/startup.yaml Adds built-in startup template.
src/ai_company/templates/builtins/dev_shop.yaml Adds built-in dev shop template.
src/ai_company/templates/builtins/product_team.yaml Adds built-in product team template.
src/ai_company/templates/builtins/agency.yaml Adds built-in agency template.
src/ai_company/templates/builtins/full_company.yaml Adds built-in full company template.
src/ai_company/templates/builtins/research_lab.yaml Adds built-in research lab template.
tests/unit/config/test_loader.py Updates tests to use shared deep_merge.
tests/unit/config/test_utils.py Adds direct unit tests for deep_merge.
tests/unit/templates/init.py Adds templates test package marker.
tests/unit/templates/conftest.py Adds fixtures and template YAML test strings.
tests/unit/templates/test_loader.py Adds unit tests for template discovery/loading and errors.
tests/unit/templates/test_renderer.py Adds unit tests for rendering pipeline, variables, and error handling.
tests/unit/templates/test_schema.py Adds unit tests for Pydantic template schema models.
tests/unit/templates/test_presets.py Adds unit tests for personality presets + auto-name generation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +387 to +392
Float value, or 0.0 on conversion failure.
"""
try:
return float(value)
except TypeError, ValueError:
return 0.0
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_safe_float() returns 0.0 on conversion failure. That makes invalid template values silently succeed (e.g., autonomy/budget typos become 0.0), which differs from normal YAML config loading where Pydantic would error on non-numeric input. Consider raising TemplateRenderError (or letting RootConfig validation fail) instead of coercing failures to 0.0.

Suggested change
Float value, or 0.0 on conversion failure.
"""
try:
return float(value)
except TypeError, ValueError:
return 0.0
Float value.
Raises:
TemplateRenderError: If the value cannot be converted to float.
"""
try:
return float(value)
except (TypeError, ValueError) as exc:
raise TemplateRenderError(
f"Invalid numeric value in rendered template: {value!r}",
) from exc

Copilot uses AI. Check for mistakes.
level: Seniority level override.
model: Model tier alias (e.g. ``"opus"``, ``"sonnet"``, ``"haiku"``).
personality_preset: Named personality preset from the presets registry.
department: Department override (``None`` uses the role's default).
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The department field docstring says None will use the role’s default department, but the current renderer implementation defaults missing departments to the literal string "engineering". Either update this docstring to match the implemented behavior, or implement role-default department resolution in the renderer (using the built-in role catalog).

Suggested change
department: Department override (``None`` uses the role's default).
department: Department override (``None`` uses the renderer's default,
currently ``"engineering"``).

Copilot uses AI. Check for mistakes.
Comment on lines +282 to +293
if not isinstance(data, dict) or "template" not in data:
msg = f"Template YAML must have a top-level 'template' key in {source_name}"
raise TemplateValidationError(
msg,
locations=(ConfigLocation(file_path=source_name),),
)

template_data = data["template"]
normalized = _normalize_template_data(template_data)
try:
return CompanyTemplate(**normalized)
except Exception as exc:
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In _parse_template_yaml, template_data = data["template"] is assumed to be a dict. If the YAML contains template: null or template: [], _normalize_template_data(template_data) will raise an AttributeError that bypasses the Template* error wrappers. Consider validating that data["template"] is a mapping and raising TemplateValidationError with a clear message when it isn’t.

Copilot uses AI. Check for mistakes.
except Exception:
logger.warning("Skipping invalid builtin template: %s", name)

return tuple(info for _, info in sorted(seen.items()))
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list_templates() docstring says “User templates are listed first”, but the returned tuple is sorted purely by template name (sorted(seen.items())). Either adjust the ordering to actually prioritize source="user" (e.g., stable sort by (source_priority, name)) or update the docstring so callers don’t rely on an ordering that isn’t guaranteed.

Suggested change
return tuple(info for _, info in sorted(seen.items()))
return tuple(
sorted(
seen.values(),
key=lambda info: (0 if info.source == "user" else 1, info.name),
)
)

Copilot uses AI. Check for mistakes.
Comment on lines +347 to +348
except ValueError:
return 0.0
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_to_float() returns 0.0 for non-numeric strings. That can silently turn invalid template values (e.g., budget_monthly: "abc") into a valid-looking config, masking user mistakes. Consider raising TemplateValidationError/TemplateRenderError (with a location) when coercion fails instead of defaulting to 0.0.

Suggested change
except ValueError:
return 0.0
except ValueError as exc:
raise ValueError(f"Invalid numeric value for template field: {value!r}") from exc

Copilot uses AI. Check for mistakes.
Comment on lines +291 to +296
agent_dict: dict[str, Any] = {
"name": name,
"role": role,
"department": agent.get("department", "engineering"),
"level": agent.get("level", "mid"),
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

department defaults to "engineering" whenever it’s omitted. The schema docstring says None should use the role’s default department, and the repo has a built-in role catalog (ai_company.core.role_catalog.get_builtin_role) that already encodes default departments. Consider deriving the department from the built-in role when not provided, and only falling back to a hard-coded default if the role is unknown.

Copilot uses AI. Check for mistakes.
Comment on lines +315 to +321
metadata = {
"name": data.get("name", ""),
"description": data.get("description", ""),
"version": data.get("version", "1.0.0"),
"company_type": company.get("type", "custom"),
"tags": tuple(data.get("tags", ())),
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TemplateMetadata supports min_agents / max_agents, but _normalize_template_data() never reads these keys from the YAML template, so users cannot set them in templates and the agent-range validators won’t reflect template intent. Consider mapping min_agents and max_agents (and validating they’re ints) from the template: YAML into metadata.

Copilot uses AI. Check for mistakes.
locations=(ConfigLocation(file_path=source_name),),
)

result: dict[str, Any] = data["template"]
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_parse_rendered_yaml() returns data["template"] without verifying it’s a mapping. If the rendered YAML produces template: null or a non-dict value, later code will crash with an AttributeError instead of raising a TemplateRenderError. Consider validating the type of data["template"] here and raising a TemplateRenderError with a clear message.

Suggested change
result: dict[str, Any] = data["template"]
result = data["template"]
if not isinstance(result, dict):
msg = (
"Rendered template 'template' section must be a mapping "
f"(dict) for {source_name}"
)
raise TemplateRenderError(
msg,
locations=(ConfigLocation(file_path=source_name),),
)

Copilot uses AI. Check for mistakes.
Comment on lines +239 to +254
return {
"company_name": company_name,
"company_type": company.get("type", template.metadata.company_type.value),
"agents": agents,
"departments": departments,
"config": {
"autonomy": _safe_float(company.get("autonomy", template.autonomy)),
"budget_monthly": _safe_float(
company.get("budget_monthly", template.budget_monthly),
),
"communication_pattern": rendered_data.get(
"communication",
template.communication,
),
},
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CompanyTemplate.workflow is parsed/validated, and templates set workflow: ..., but _build_config_dict() never carries this into the generated RootConfig (there’s no corresponding field in default_config_dict() / RootConfig). As-is, changing workflow in a template has no effect. Either plumb workflow into an existing config field (if one exists) or drop it from the template schema/templates to avoid a misleading no-op setting.

Copilot uses AI. Check for mistakes.
…, and Copilot

- Narrow exception handling: replace broad `except Exception` with specific types
- Add path traversal protection in template loader
- Add filesystem error handling for template file reads (OSError, UnicodeDecodeError)
- Extract `to_float()` utility to config/utils.py for strict numeric validation
- Add type-match validation for TemplateVariable defaults
- Strengthen type hints: use NotBlankStr | None for optional identifiers
- Add __str__ method to TemplateValidationError with per-field details
- Remove duplicate TestDeepMerge class from test_loader.py
- Fix docstrings: module-level, class-level, and field descriptions
- Include exception details in warning logs
- Add template data type guard before Pydantic validation
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ai_company/config/loader.py (1)

371-443: 🛠️ Refactor suggestion | 🟠 Major

Refactor load_config into smaller helpers to satisfy function-size rule.

load_config is currently too long; split the phases (resolve path, parse/merge layers, env substitution, validate) into helper functions to keep this public function under the limit.

♻️ Suggested refactor shape
 def load_config(
     config_path: Path | str | None = None,
     *,
     override_paths: tuple[Path | str, ...] = (),
 ) -> RootConfig:
-    if config_path is None:
-        config_path = discover_config()
-    config_path = Path(config_path)
-
-    merged = default_config_dict()
-    yaml_text = _read_config_text(config_path)
-    primary = _parse_yaml_string(yaml_text, str(config_path))
-    merged = deep_merge(merged, primary)
-
-    for override_path in override_paths:
-        override = _parse_yaml_file(Path(override_path))
-        merged = deep_merge(merged, override)
-
-    merged = _substitute_env_vars(merged, source_file="<merged config>")
-    line_map = _build_line_map(yaml_text)
-    return _validate_config_dict(
-        merged,
-        source_file=str(config_path),
-        line_map=line_map,
-    )
+    resolved_path = _resolve_config_path(config_path)
+    merged, yaml_text = _load_merged_layers(resolved_path, override_paths)
+    merged = _substitute_env_vars(merged, source_file="<merged config>")
+    return _validate_with_primary_line_map(merged, yaml_text, resolved_path)

As per coding guidelines "Keep functions under 50 lines and files under 800 lines".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/config/loader.py` around lines 371 - 443, The load_config
function should be split into small helpers: extract path resolution into a
helper (resolve_config_path using discover_config), extract primary file
reading/parsing and line-map construction into read_and_parse_primary (calls
_read_config_text, _parse_yaml_string and _build_line_map), extract override
merging into apply_overrides (iterates override_paths and calls _parse_yaml_file
+ deep_merge), extract env substitution into substitute_env (calls
_substitute_env_vars with source_file="<merged config>"), and keep validation in
validate_config (wraps _validate_config_dict). Refactor load_config to call
these helpers in order (default_config_dict -> read_and_parse_primary ->
apply_overrides -> substitute_env -> validate_config) so load_config stays short
while reusing the existing functions: load_config, discover_config,
default_config_dict, _read_config_text, _parse_yaml_string, deep_merge,
_parse_yaml_file, _substitute_env_vars, _build_line_map, and
_validate_config_dict.
♻️ Duplicate comments (1)
src/ai_company/templates/loader.py (1)

382-407: ⚠️ Potential issue | 🟠 Major

Avoid broad 0.0 fallback in _to_float; only allow placeholder fallback.

Current behavior masks invalid numeric values that should fail validation.

💡 Proposed fix
 def _to_float(value: Any) -> float:
@@
-    if value is None:
+    if value is None or value == "__JINJA2__":
         return 0.0
     try:
         return float(value)
-    except (TypeError, ValueError):
-        logger.debug(
-            "Cannot convert %r to float in Pass 1 "
-            "(may be a Jinja2 placeholder), using 0.0",
-            value,
-        )
-        return 0.0
+    except (TypeError, ValueError) as exc:
+        msg = f"Invalid numeric value in template pass 1: {value!r}"
+        raise ValueError(msg) from exc

As per coding guidelines, "Validate at system boundaries (user input, external APIs, config files); never silently swallow errors".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/loader.py` around lines 382 - 407, The helper
_to_float currently swallows all TypeError/ValueError and returns 0.0, masking
real bad input; change it so that it only returns 0.0 for known placeholder
patterns (e.g. Jinja2 placeholders like "__JINJA2__" or a regex match) and
otherwise re-raise the original exception so upstream validation fails; update
the except block in _to_float to detect placeholder strings (log a debug message
and return 0.0) but for any other conversion error re-raise the
ValueError/TypeError so invalid numeric values are not silently accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ai_company/templates/loader.py`:
- Around line 79-132: The list_templates function (and similarly
_parse_template_yaml) is too large and mixes responsibilities; split
list_templates into smaller helpers such as _collect_user_templates(path: Path)
-> dict[str, TemplateInfo], _collect_builtin_templates(seen: dict[str,
TemplateInfo]) -> None, and _make_template_info(name, loaded, source) ->
TemplateInfo to isolate loading, metadata extraction, error handling and
sorting; call existing helpers _load_from_file and _load_builtin inside these
new functions and move the try/except/logging into each helper (use
logger.warning for user template errors and logger.exception for builtin
errors), then have list_templates simply call the two collectors, sort seen and
return the tuple. Apply the same decomposition to _parse_template_yaml by
extracting YAML parsing, validation, and metadata extraction into focused helper
functions and reusing them from the caller.
- Around line 359-365: When building metadata, avoid calling company.get on
non-mappings: read company_raw = data.get("company", None), validate it with
isinstance(company_raw, Mapping) (or dict) and if it's None set company = {} but
if it's not a mapping raise a clear validation error (e.g., ValueError or custom
ValidationError) before proceeding to construct metadata; update subsequent uses
(the metadata dict creation that references company.get and the surrounding
normalization code) to use the validated company variable.
- Line 125: Replace the Python 2 style exception clause that uses commas with a
parenthesized tuple: change "except TemplateRenderError,
TemplateValidationError, OSError:" to "except (TemplateRenderError,
TemplateValidationError, OSError):" (and if you need the exception instance, use
"except (TemplateRenderError, TemplateValidationError, OSError) as e:"). Update
both occurrences that reference TemplateRenderError, TemplateValidationError,
and OSError (the except block around the template rendering/validation logic and
the other similar except at the later occurrence) so the syntax is valid in
Python 3.

In `@src/ai_company/templates/renderer.py`:
- Around line 219-275: _build_config_dict and _expand_agents are doing multiple
tasks (parsing company fields, numeric validation, agent expansion and
department construction) and exceed the size limit; split them into smaller
focused helpers so each function is under ~50 lines. Specifically, extract: a
parse_company_metadata or _extract_company_fields(rendered_data, template,
variables) to compute company_name/type and source_name, a
_parse_config_values(rendered_data, template) that handles
autonomy/budget_monthly parsing and raises TemplateRenderError on ValueError,
and leave _build_config_dict to orchestrate calling _expand_agents(raw_agents)
and _build_departments(raw_depts) and assembling the final dict; also move any
numeric conversion logic from _expand_agents into the new _parse_config_values
to keep responsibilities separated. Ensure new helper names
(_extract_company_fields, _parse_config_values) are referenced by
_build_config_dict and update imports/uses accordingly so tests still pass.
- Around line 320-331: The code currently catches KeyError from
get_personality_preset(preset_name) and only logs a warning, silently using a
default; instead, make invalid personality presets fail rendering by removing
the silent fallback and raising a clear error: when
get_personality_preset(preset_name) raises KeyError (or when preset_name not in
PERSONALITY_PRESETS), raise an explicit exception (e.g., ValueError or re-raise
KeyError) that includes preset_name, agent name, and the sorted
PERSONALITY_PRESETS so the renderer fails loudly; update the logic around
agent_dict["personality"], get_personality_preset, and any logger.warning usage
to ensure the error propagates rather than being swallowed.
- Around line 234-247: Rendered values from rendered_data (company, agents,
departments) must be validated and converted to proper shapes before use; update
the block that reads rendered_data in renderer.py to check that company is a
dict (and that company_name is a str or derive from template.metadata.name),
that raw_agents is a list before calling _expand_agents, and that raw_depts is a
list before calling _build_departments, and raise TemplateRenderError with a
clear message when any of those validations fail; add these checks around the
existing uses of rendered_data, company, _expand_agents(raw_agents) and
_build_departments(raw_depts) so invalid YAML shapes produce TemplateRenderError
with context instead of raw attribute/type errors.

In `@src/ai_company/templates/schema.py`:
- Line 29: The models that parse external YAML should reject unknown fields:
update each model's model_config (for TemplateVariable, TemplateAgentConfig,
TemplateDepartmentConfig, TemplateMetadata, and CompanyTemplate) to include
extra="forbid" (e.g., extend the existing ConfigDict(frozen=True) to also set
extra="forbid") so Pydantic will raise on unknown fields instead of silently
ignoring them. Ensure you modify the model_config in each named class
(TemplateVariable, TemplateAgentConfig, TemplateDepartmentConfig,
TemplateMetadata, CompanyTemplate).
- Around line 53-60: The validation currently uses type_map and
isinstance(self.default, expected) which accepts bool for var_type "int" and
"float" because bool subclasses int; modify the check in the schema validation
(symbols: type_map, expected, self.var_type, self.default, isinstance) to
explicitly reject booleans for numeric types—either by adding a guard like "if
isinstance(self.default, bool) and self.var_type in ('int','float'): raise
TypeError" before the isinstance check, or by comparing types directly
(type(self.default) is int or float) so bools are not accepted; implement one of
these approaches and ensure the error message mentions the variable
name/var_type for clarity.

---

Outside diff comments:
In `@src/ai_company/config/loader.py`:
- Around line 371-443: The load_config function should be split into small
helpers: extract path resolution into a helper (resolve_config_path using
discover_config), extract primary file reading/parsing and line-map construction
into read_and_parse_primary (calls _read_config_text, _parse_yaml_string and
_build_line_map), extract override merging into apply_overrides (iterates
override_paths and calls _parse_yaml_file + deep_merge), extract env
substitution into substitute_env (calls _substitute_env_vars with
source_file="<merged config>"), and keep validation in validate_config (wraps
_validate_config_dict). Refactor load_config to call these helpers in order
(default_config_dict -> read_and_parse_primary -> apply_overrides ->
substitute_env -> validate_config) so load_config stays short while reusing the
existing functions: load_config, discover_config, default_config_dict,
_read_config_text, _parse_yaml_string, deep_merge, _parse_yaml_file,
_substitute_env_vars, _build_line_map, and _validate_config_dict.

---

Duplicate comments:
In `@src/ai_company/templates/loader.py`:
- Around line 382-407: The helper _to_float currently swallows all
TypeError/ValueError and returns 0.0, masking real bad input; change it so that
it only returns 0.0 for known placeholder patterns (e.g. Jinja2 placeholders
like "__JINJA2__" or a regex match) and otherwise re-raise the original
exception so upstream validation fails; update the except block in _to_float to
detect placeholder strings (log a debug message and return 0.0) but for any
other conversion error re-raise the ValueError/TypeError so invalid numeric
values are not silently accepted.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c4f3a0 and 0ad6519.

📒 Files selected for processing (7)
  • src/ai_company/config/loader.py
  • src/ai_company/config/utils.py
  • src/ai_company/templates/errors.py
  • src/ai_company/templates/loader.py
  • src/ai_company/templates/renderer.py
  • src/ai_company/templates/schema.py
  • tests/unit/config/test_loader.py
💤 Files with no reviewable changes (1)
  • tests/unit/config/test_loader.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (actions)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use PEP 649 native lazy annotations instead of from __future__ import annotations (Python 3.14+)
All public functions and classes must include type hints; enforce via mypy strict mode
Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)
Keep functions under 50 lines and files under 800 lines
Create new objects instead of mutating existing ones; enforce immutability patterns
Use Pydantic v2 models (BaseModel, model_validator, ConfigDict) for data validation
Validate at system boundaries (user input, external APIs, config files); never silently swallow errors
Enforce 88-character line length (via ruff configuration)

Files:

  • src/ai_company/config/utils.py
  • src/ai_company/templates/loader.py
  • src/ai_company/templates/errors.py
  • src/ai_company/templates/renderer.py
  • src/ai_company/templates/schema.py
  • src/ai_company/config/loader.py
{src/**/*.py,tests/**/*.py}

📄 CodeRabbit inference engine (CLAUDE.md)

Run ruff check and ruff format as pre-commit hooks on all Python files; enforce no commits to main branch

Files:

  • src/ai_company/config/utils.py
  • src/ai_company/templates/loader.py
  • src/ai_company/templates/errors.py
  • src/ai_company/templates/renderer.py
  • src/ai_company/templates/schema.py
  • src/ai_company/config/loader.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-26T17:43:50.902Z
Learning: Applies to src/prompts/**/*.py : YAML templates must be loaded once at startup under lock protection for thread safety.
📚 Learning: 2026-02-26T17:43:50.902Z
Learnt from: CR
Repo: Aureliolo/story-factory PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-26T17:43:50.902Z
Learning: Applies to src/prompts/**/*.py : YAML templates must be loaded once at startup under lock protection for thread safety.

Applied to files:

  • src/ai_company/templates/loader.py
📚 Learning: 2026-02-28T13:41:23.377Z
Learnt from: CR
Repo: Aureliolo/ai-company PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-28T13:41:23.377Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files); never silently swallow errors

Applied to files:

  • src/ai_company/templates/loader.py
  • src/ai_company/templates/renderer.py
🧬 Code graph analysis (5)
src/ai_company/templates/loader.py (3)
src/ai_company/config/errors.py (1)
  • ConfigLocation (7-21)
src/ai_company/templates/errors.py (3)
  • TemplateNotFoundError (10-11)
  • TemplateRenderError (14-20)
  • TemplateValidationError (23-59)
src/ai_company/templates/schema.py (1)
  • CompanyTemplate (167-260)
src/ai_company/templates/errors.py (1)
src/ai_company/config/errors.py (2)
  • ConfigError (24-59)
  • ConfigLocation (7-21)
src/ai_company/templates/renderer.py (8)
src/ai_company/templates/errors.py (3)
  • TemplateError (6-7)
  • TemplateRenderError (14-20)
  • TemplateValidationError (23-59)
src/ai_company/config/defaults.py (1)
  • default_config_dict (6-28)
src/ai_company/config/errors.py (1)
  • ConfigLocation (7-21)
src/ai_company/config/schema.py (1)
  • RootConfig (199-323)
src/ai_company/config/utils.py (2)
  • deep_merge (30-54)
  • to_float (7-27)
src/ai_company/templates/presets.py (2)
  • generate_auto_name (87-103)
  • get_personality_preset (67-84)
src/ai_company/templates/loader.py (1)
  • LoadedTemplate (65-76)
src/ai_company/templates/schema.py (1)
  • CompanyTemplate (167-260)
src/ai_company/templates/schema.py (1)
src/ai_company/core/enums.py (2)
  • CompanyType (72-82)
  • SeniorityLevel (6-21)
src/ai_company/config/loader.py (2)
src/ai_company/config/utils.py (1)
  • deep_merge (30-54)
src/ai_company/config/defaults.py (1)
  • default_config_dict (6-28)
🔇 Additional comments (7)
src/ai_company/config/loader.py (2)

423-429: Good move centralizing merge behavior via deep_merge.

Using one shared merge utility on Lines [423], [429], and [468] improves consistency and preserves non-mutating semantics across load paths.

Also applies to: 468-468


190-195: Logging improvement is solid.

Including the caught exception in the warning on Lines [190-195] materially improves diagnosability when AST line mapping fails.

src/ai_company/config/utils.py (2)

7-27: to_float validation and error reporting look correct.

The explicit None guard and wrapped conversion errors provide clear boundary validation behavior for callers.


30-54: deep_merge implementation is clean and non-mutating.

Recursive dict merge with deep copies is consistent with immutable layering semantics and suitable as a shared config utility.

src/ai_company/templates/errors.py (1)

6-59: Exception hierarchy and validation-error formatting look solid.

The layering and TemplateValidationError.__str__ behavior are clear and robust.

src/ai_company/templates/renderer.py (1)

202-216: Good defensive validation of rendered YAML structure.

The explicit 'template' mapping checks are correct and prevent opaque downstream crashes.

src/ai_company/templates/loader.py (1)

157-165: Path traversal guard is a good defensive control.

The normalized-name check correctly blocks path separator input before user-directory lookup.

Comment on lines +79 to +132
def list_templates() -> tuple[TemplateInfo, ...]:
"""Return all available templates (user directory + built-in).

User templates override built-in templates of the same name.
The result is sorted alphabetically by template name. Templates
that fail to load are silently skipped with a warning log.

Returns:
Sorted tuple of :class:`TemplateInfo` objects.
"""
seen: dict[str, TemplateInfo] = {}

# User templates (higher priority).
if _USER_TEMPLATES_DIR.is_dir():
for path in sorted(
p for p in _USER_TEMPLATES_DIR.glob("*.yaml") if p.is_file()
):
name = path.stem
try:
loaded = _load_from_file(path)
meta = loaded.template.metadata
seen[name] = TemplateInfo(
name=name,
display_name=meta.name,
description=meta.description,
source="user",
)
except (TemplateRenderError, TemplateValidationError, OSError) as exc:
logger.warning(
"Skipping invalid user template %s: %s",
path,
exc,
)

# Built-in templates (lower priority).
for name in sorted(BUILTIN_TEMPLATES):
if name not in seen:
try:
loaded = _load_builtin(name)
meta = loaded.template.metadata
seen[name] = TemplateInfo(
name=name,
display_name=meta.name,
description=meta.description,
source="builtin",
)
except TemplateRenderError, TemplateValidationError, OSError:
logger.exception(
"Built-in template %r is invalid (packaging defect)",
name,
)

return tuple(info for _, info in sorted(seen.items()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Refactor oversized functions into smaller helpers.

list_templates and _parse_template_yaml exceed the function-size guideline and combine multiple responsibilities.

As per coding guidelines, "Keep functions under 50 lines and files under 800 lines".

Also applies to: 286-345

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/loader.py` around lines 79 - 132, The list_templates
function (and similarly _parse_template_yaml) is too large and mixes
responsibilities; split list_templates into smaller helpers such as
_collect_user_templates(path: Path) -> dict[str, TemplateInfo],
_collect_builtin_templates(seen: dict[str, TemplateInfo]) -> None, and
_make_template_info(name, loaded, source) -> TemplateInfo to isolate loading,
metadata extraction, error handling and sorting; call existing helpers
_load_from_file and _load_builtin inside these new functions and move the
try/except/logging into each helper (use logger.warning for user template errors
and logger.exception for builtin errors), then have list_templates simply call
the two collectors, sort seen and return the tuple. Apply the same decomposition
to _parse_template_yaml by extracting YAML parsing, validation, and metadata
extraction into focused helper functions and reusing them from the caller.

description=meta.description,
source="builtin",
)
except TemplateRenderError, TemplateValidationError, OSError:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check if file exists and get line count
wc -l src/ai_company/templates/loader.py

# View the specific lines mentioned (125 and 401)
sed -n '120,130p' src/ai_company/templates/loader.py
sed -n '396,406p' src/ai_company/templates/loader.py

Repository: Aureliolo/ai-company

Length of output: 789


🏁 Script executed:

# Verify the exact Python version constraint and check if there are syntax checks in place
head -20 src/ai_company/templates/loader.py

# Check if file can be parsed as Python
python3 -m py_compile src/ai_company/templates/loader.py 2>&1 || echo "Compilation failed"

Repository: Aureliolo/ai-company

Length of output: 917


Fix invalid Python exception syntax.

Lines 125 and 401 use Python 2 style comma-separated exception handling which causes SyntaxError: multiple exception types must be parenthesized and prevents module import.

Proposed fix
-            except TemplateRenderError, TemplateValidationError, OSError:
+            except (TemplateRenderError, TemplateValidationError, OSError):
@@
-    except TypeError, ValueError:
+    except (TypeError, ValueError):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/loader.py` at line 125, Replace the Python 2 style
exception clause that uses commas with a parenthesized tuple: change "except
TemplateRenderError, TemplateValidationError, OSError:" to "except
(TemplateRenderError, TemplateValidationError, OSError):" (and if you need the
exception instance, use "except (TemplateRenderError, TemplateValidationError,
OSError) as e:"). Update both occurrences that reference TemplateRenderError,
TemplateValidationError, and OSError (the except block around the template
rendering/validation logic and the other similar except at the later occurrence)
so the syntax is valid in Python 3.

Comment on lines +219 to +275
def _build_config_dict(
rendered_data: dict[str, Any],
template: CompanyTemplate,
variables: dict[str, Any],
) -> dict[str, Any]:
"""Build a RootConfig-compatible dict from rendered template data.

Args:
rendered_data: Parsed dict from the rendered YAML.
template: Original template metadata (for fallback values).
variables: Collected variables.

Returns:
Dict suitable for ``RootConfig(**deep_merge(defaults, result))``.
"""
company = rendered_data.get("company", {})
company_name = variables.get(
"company_name",
template.metadata.name,
)

# Expand agents.
raw_agents = rendered_data.get("agents", [])
agents = _expand_agents(raw_agents)

# Build departments for RootConfig.
raw_depts = rendered_data.get("departments", [])
departments = _build_departments(raw_depts)

source_name = template.metadata.name
try:
autonomy = to_float(
company.get("autonomy", template.autonomy),
field_name="autonomy",
)
budget_monthly = to_float(
company.get("budget_monthly", template.budget_monthly),
field_name="budget_monthly",
)
except ValueError as exc:
msg = f"Invalid numeric value in rendered template {source_name!r}: {exc}"
raise TemplateRenderError(msg) from exc

return {
"company_name": company_name,
"company_type": company.get("type", template.metadata.company_type.value),
"agents": agents,
"departments": departments,
"config": {
"autonomy": autonomy,
"budget_monthly": budget_monthly,
"communication_pattern": rendered_data.get(
"communication",
template.communication,
),
},
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Split oversized helper functions into smaller units.

_build_config_dict and _expand_agents currently mix multiple responsibilities and exceed the size limit.

As per coding guidelines, "Keep functions under 50 lines and files under 800 lines".

Also applies to: 278-339

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/renderer.py` around lines 219 - 275,
_build_config_dict and _expand_agents are doing multiple tasks (parsing company
fields, numeric validation, agent expansion and department construction) and
exceed the size limit; split them into smaller focused helpers so each function
is under ~50 lines. Specifically, extract: a parse_company_metadata or
_extract_company_fields(rendered_data, template, variables) to compute
company_name/type and source_name, a _parse_config_values(rendered_data,
template) that handles autonomy/budget_monthly parsing and raises
TemplateRenderError on ValueError, and leave _build_config_dict to orchestrate
calling _expand_agents(raw_agents) and _build_departments(raw_depts) and
assembling the final dict; also move any numeric conversion logic from
_expand_agents into the new _parse_config_values to keep responsibilities
separated. Ensure new helper names (_extract_company_fields,
_parse_config_values) are referenced by _build_config_dict and update
imports/uses accordingly so tests still pass.

Comment on lines +320 to +331
try:
agent_dict["personality"] = get_personality_preset(preset_name)
except KeyError:
available = sorted(PERSONALITY_PRESETS)
logger.warning(
"Unknown personality preset %r for agent %r; "
"using default personality. Available presets: %s",
preset_name,
name,
available,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not silently downgrade invalid personality_preset to a warning.

An unknown preset should fail rendering so template errors are explicit and actionable.

💡 Proposed fix
         if preset_name:
             try:
                 agent_dict["personality"] = get_personality_preset(preset_name)
-            except KeyError:
+            except KeyError as exc:
                 available = sorted(PERSONALITY_PRESETS)
-                logger.warning(
-                    "Unknown personality preset %r for agent %r; "
-                    "using default personality. Available presets: %s",
-                    preset_name,
-                    name,
-                    available,
-                )
+                msg = (
+                    f"Unknown personality preset {preset_name!r} for agent {name!r}. "
+                    f"Available presets: {available}"
+                )
+                raise TemplateRenderError(msg) from exc

As per coding guidelines, "Validate at system boundaries (user input, external APIs, config files); never silently swallow errors".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
agent_dict["personality"] = get_personality_preset(preset_name)
except KeyError:
available = sorted(PERSONALITY_PRESETS)
logger.warning(
"Unknown personality preset %r for agent %r; "
"using default personality. Available presets: %s",
preset_name,
name,
available,
)
try:
agent_dict["personality"] = get_personality_preset(preset_name)
except KeyError as exc:
available = sorted(PERSONALITY_PRESETS)
msg = (
f"Unknown personality preset {preset_name!r} for agent {name!r}. "
f"Available presets: {available}"
)
raise TemplateRenderError(msg) from exc
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ai_company/templates/renderer.py` around lines 320 - 331, The code
currently catches KeyError from get_personality_preset(preset_name) and only
logs a warning, silently using a default; instead, make invalid personality
presets fail rendering by removing the silent fallback and raising a clear
error: when get_personality_preset(preset_name) raises KeyError (or when
preset_name not in PERSONALITY_PRESETS), raise an explicit exception (e.g.,
ValueError or re-raise KeyError) that includes preset_name, agent name, and the
sorted PERSONALITY_PRESETS so the renderer fails loudly; update the logic around
agent_dict["personality"], get_personality_preset, and any logger.warning usage
to ensure the error propagates rather than being swallowed.

- Guard template.company type before calling .get() in normalization
- Add extra="forbid" to all 5 template schema models (catches YAML typos)
- Reject bool defaults for int/float var_type (bool subclasses int)
- Validate company/agents/departments shapes in renderer before use
Copilot AI review requested due to automatic review settings March 1, 2026 08:02
@Aureliolo Aureliolo merged commit cbf1496 into main Mar 1, 2026
11 of 12 checks passed
@Aureliolo Aureliolo deleted the feat/company-templates branch March 1, 2026 08:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 27 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +326 to +329
agent_dict: dict[str, Any] = {
"name": name,
"role": role,
"department": agent.get("department", "engineering"),
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renderer behavior doesn't match the schema/docstring promise that department=None defaults to "engineering" during rendering. agent.get("department", "engineering") returns None when the key exists with a YAML null value, which then fails RootConfig validation. Treat missing/None/blank departments as the default (or raise a clear TemplateRenderError).

Suggested change
agent_dict: dict[str, Any] = {
"name": name,
"role": role,
"department": agent.get("department", "engineering"),
# Normalize department: treat missing/None/blank as the default "engineering".
raw_department = agent.get("department")
if raw_department is None:
department = "engineering"
elif isinstance(raw_department, str) and not raw_department.strip():
department = "engineering"
else:
department = raw_department
agent_dict: dict[str, Any] = {
"name": name,
"role": role,
"department": department,

Copilot uses AI. Check for mistakes.
Comment on lines +125 to +129
except TemplateRenderError, TemplateValidationError, OSError:
logger.exception(
"Built-in template %r is invalid (packaging defect)",
name,
)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except TemplateRenderError, TemplateValidationError, OSError: is invalid Python syntax (multiple exception types must be parenthesized). This will prevent the module from importing and break template listing entirely. Use except (TemplateRenderError, TemplateValidationError, OSError) as exc: (or drop as exc if unused).

Copilot uses AI. Check for mistakes.
Comment on lines +310 to +316
for idx, agent in enumerate(raw_agents):
role = agent.get("role", "Agent")
name = str(agent.get("name", "")).strip()

# Auto-generate name if empty or still a Jinja2 expression.
if not name or name.startswith("{{"):
name = generate_auto_name(role, seed=idx)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

role = agent.get("role", "Agent") can be None if the rendered YAML explicitly sets role: null, and generate_auto_name(role, ...) will then crash because generate_auto_name calls .strip() on the role. Coerce role to a non-empty string (and raise a TemplateRenderError if it's missing/blank) before calling generate_auto_name.

Copilot uses AI. Check for mistakes.
Comment on lines +311 to +316
role = agent.get("role", "Agent")
name = str(agent.get("name", "")).strip()

# Auto-generate name if empty or still a Jinja2 expression.
if not name or name.startswith("{{"):
name = generate_auto_name(role, seed=idx)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name = str(agent.get("name", "")).strip() turns a YAML null into the literal string "None", which will bypass the auto-name logic and then fail RootConfig validation expectations (or silently create an agent actually named "None"). Treat None (and other non-string scalars) as empty for naming purposes, e.g. name_value = agent.get("name"); name = "" if name_value is None else str(name_value).strip().

Copilot uses AI. Check for mistakes.
expanded: list[dict[str, Any]] = []
used_names: set[str] = set()

for idx, agent in enumerate(raw_agents):
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_expand_agents assumes every item in raw_agents is a mapping; if the YAML contains a non-mapping entry (e.g. - "CEO"), this will raise an AttributeError at agent.get(...) rather than a TemplateRenderError with useful context. Validate isinstance(agent, dict) and raise TemplateRenderError when invalid entries are encountered.

Suggested change
for idx, agent in enumerate(raw_agents):
for idx, agent in enumerate(raw_agents):
if not isinstance(agent, dict):
raise TemplateRenderError(
f"Invalid agent entry at index {idx}: expected a mapping/dict, "
f"got {type(agent).__name__!r} with value {agent!r}."
)

Copilot uses AI. Check for mistakes.
Comment on lines +369 to +374
for dept in raw_depts:
try:
budget_pct = to_float(
dept.get("budget_percent", 0.0),
field_name=f"departments[{dept.get('name', '')}].budget_percent",
)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_build_departments assumes every item in raw_depts is a mapping; invalid YAML like departments: ["engineering"] will raise AttributeError on dept.get(...) instead of a structured TemplateRenderError. Add an isinstance(dept, dict) check and raise TemplateRenderError with the offending value (and ideally the index) when invalid entries are present.

Copilot uses AI. Check for mistakes.
Comment on lines +264 to +276
source_name = template.metadata.name
try:
autonomy = to_float(
company.get("autonomy", template.autonomy),
field_name="autonomy",
)
budget_monthly = to_float(
company.get("budget_monthly", template.budget_monthly),
field_name="budget_monthly",
)
except ValueError as exc:
msg = f"Invalid numeric value in rendered template {source_name!r}: {exc}"
raise TemplateRenderError(msg) from exc
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In _build_config_dict, source_name is set to template.metadata.name and then used in error messages for numeric coercion failures. This loses the actual template source (e.g. <builtin:startup> or a file path) which is available via loaded.source_name in the caller, making errors harder to trace. Prefer using the real loaded.source_name (or pass it into _build_config_dict) for all render/parse error messages.

Copilot uses AI. Check for mistakes.
Aureliolo added a commit that referenced this pull request Mar 10, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.1](ai-company-v0.1.0...ai-company-v0.1.1)
(2026-03-10)


### Features

* add autonomy levels and approval timeout policies
([#42](#42),
[#126](#126))
([#197](#197))
([eecc25a](eecc25a))
* add CFO cost optimization service with anomaly detection, reports, and
approval decisions
([#186](#186))
([a7fa00b](a7fa00b))
* add code quality toolchain (ruff, mypy, pre-commit, dependabot)
([#63](#63))
([36681a8](36681a8))
* add configurable cost tiers and subscription/quota-aware tracking
([#67](#67))
([#185](#185))
([9baedfa](9baedfa))
* add container packaging, Docker Compose, and CI pipeline
([#269](#269))
([435bdfe](435bdfe)),
closes [#267](#267)
* add coordination error taxonomy classification pipeline
([#146](#146))
([#181](#181))
([70c7480](70c7480))
* add cost-optimized, hierarchical, and auction assignment strategies
([#175](#175))
([ce924fa](ce924fa)),
closes [#173](#173)
* add design specification, license, and project setup
([8669a09](8669a09))
* add env var substitution and config file auto-discovery
([#77](#77))
([7f53832](7f53832))
* add FastestStrategy routing + vendor-agnostic cleanup
([#140](#140))
([09619cb](09619cb)),
closes [#139](#139)
* add HR engine and performance tracking
([#45](#45),
[#47](#47))
([#193](#193))
([2d091ea](2d091ea))
* add issue auto-search and resolution verification to PR review skill
([#119](#119))
([deecc39](deecc39))
* add memory retrieval, ranking, and context injection pipeline
([#41](#41))
([873b0aa](873b0aa))
* add pluggable MemoryBackend protocol with models, config, and events
([#180](#180))
([46cfdd4](46cfdd4))
* add pluggable MemoryBackend protocol with models, config, and events
([#32](#32))
([46cfdd4](46cfdd4))
* add pluggable PersistenceBackend protocol with SQLite implementation
([#36](#36))
([f753779](f753779))
* add progressive trust and promotion/demotion subsystems
([#43](#43),
[#49](#49))
([3a87c08](3a87c08))
* add retry handler, rate limiter, and provider resilience
([#100](#100))
([b890545](b890545))
* add SecOps security agent with rule engine, audit log, and ToolInvoker
integration ([#40](#40))
([83b7b6c](83b7b6c))
* add shared org memory and memory consolidation/archival
([#125](#125),
[#48](#48))
([4a0832b](4a0832b))
* design unified provider interface
([#86](#86))
([3e23d64](3e23d64))
* expand template presets, rosters, and add inheritance
([#80](#80),
[#81](#81),
[#84](#84))
([15a9134](15a9134))
* implement agent runtime state vs immutable config split
([#115](#115))
([4cb1ca5](4cb1ca5))
* implement AgentEngine core orchestrator
([#11](#11))
([#143](#143))
([f2eb73a](f2eb73a))
* implement basic tool system (registry, invocation, results)
([#15](#15))
([c51068b](c51068b))
* implement built-in file system tools
([#18](#18))
([325ef98](325ef98))
* implement communication foundation — message bus, dispatcher, and
messenger ([#157](#157))
([8e71bfd](8e71bfd))
* implement company template system with 7 built-in presets
([#85](#85))
([cbf1496](cbf1496))
* implement conflict resolution protocol
([#122](#122))
([#166](#166))
([e03f9f2](e03f9f2))
* implement core entity and role system models
([#69](#69))
([acf9801](acf9801))
* implement crash recovery with fail-and-reassign strategy
([#149](#149))
([e6e91ed](e6e91ed))
* implement engine extensions — Plan-and-Execute loop and call
categorization
([#134](#134),
[#135](#135))
([#159](#159))
([9b2699f](9b2699f))
* implement enterprise logging system with structlog
([#73](#73))
([2f787e5](2f787e5))
* implement graceful shutdown with cooperative timeout strategy
([#130](#130))
([6592515](6592515))
* implement hierarchical delegation and loop prevention
([#12](#12),
[#17](#17))
([6be60b6](6be60b6))
* implement LiteLLM driver and provider registry
([#88](#88))
([ae3f18b](ae3f18b)),
closes [#4](#4)
* implement LLM decomposition strategy and workspace isolation
([#174](#174))
([aa0eefe](aa0eefe))
* implement meeting protocol system
([#123](#123))
([ee7caca](ee7caca))
* implement message and communication domain models
([#74](#74))
([560a5d2](560a5d2))
* implement model routing engine
([#99](#99))
([d3c250b](d3c250b))
* implement parallel agent execution
([#22](#22))
([#161](#161))
([65940b3](65940b3))
* implement per-call cost tracking service
([#7](#7))
([#102](#102))
([c4f1f1c](c4f1f1c))
* implement personality injection and system prompt construction
([#105](#105))
([934dd85](934dd85))
* implement single-task execution lifecycle
([#21](#21))
([#144](#144))
([c7e64e4](c7e64e4))
* implement subprocess sandbox for tool execution isolation
([#131](#131))
([#153](#153))
([3c8394e](3c8394e))
* implement task assignment subsystem with pluggable strategies
([#172](#172))
([c7f1b26](c7f1b26)),
closes [#26](#26)
[#30](#30)
* implement task decomposition and routing engine
([#14](#14))
([9c7fb52](9c7fb52))
* implement Task, Project, Artifact, Budget, and Cost domain models
([#71](#71))
([81eabf1](81eabf1))
* implement tool permission checking
([#16](#16))
([833c190](833c190))
* implement YAML config loader with Pydantic validation
([#59](#59))
([ff3a2ba](ff3a2ba))
* implement YAML config loader with Pydantic validation
([#75](#75))
([ff3a2ba](ff3a2ba))
* initialize project with uv, hatchling, and src layout
([39005f9](39005f9))
* initialize project with uv, hatchling, and src layout
([#62](#62))
([39005f9](39005f9))
* Litestar REST API, WebSocket feed, and approval queue (M6)
([#189](#189))
([29fcd08](29fcd08))
* make TokenUsage.total_tokens a computed field
([#118](#118))
([c0bab18](c0bab18)),
closes [#109](#109)
* parallel tool execution in ToolInvoker.invoke_all
([#137](#137))
([58517ee](58517ee))
* testing framework, CI pipeline, and M0 gap fixes
([#64](#64))
([f581749](f581749))
* wire all modules into observability system
([#97](#97))
([f7a0617](f7a0617))


### Bug Fixes

* address Greptile post-merge review findings from PRs
[#170](https://github.com/Aureliolo/ai-company/issues/170)-[#175](https://github.com/Aureliolo/ai-company/issues/175)
([#176](#176))
([c5ca929](c5ca929))
* address post-merge review feedback from PRs
[#164](https://github.com/Aureliolo/ai-company/issues/164)-[#167](https://github.com/Aureliolo/ai-company/issues/167)
([#170](#170))
([3bf897a](3bf897a)),
closes [#169](#169)
* enforce strict mypy on test files
([#89](#89))
([aeeff8c](aeeff8c))
* harden Docker sandbox, MCP bridge, and code runner
([#50](#50),
[#53](#53))
([d5e1b6e](d5e1b6e))
* harden git tools security + code quality improvements
([#150](#150))
([000a325](000a325))
* harden subprocess cleanup, env filtering, and shutdown resilience
([#155](#155))
([d1fe1fb](d1fe1fb))
* incorporate post-merge feedback + pre-PR review fixes
([#164](#164))
([c02832a](c02832a))
* pre-PR review fixes for post-merge findings
([#183](#183))
([26b3108](26b3108))
* strengthen immutability for BaseTool schema and ToolInvoker boundaries
([#117](#117))
([7e5e861](7e5e861))


### Performance

* harden non-inferable principle implementation
([#195](#195))
([02b5f4e](02b5f4e)),
closes [#188](#188)


### Refactoring

* adopt NotBlankStr across all models
([#108](#108))
([#120](#120))
([ef89b90](ef89b90))
* extract _SpendingTotals base class from spending summary models
([#111](#111))
([2f39c1b](2f39c1b))
* harden BudgetEnforcer with error handling, validation extraction, and
review fixes
([#182](#182))
([c107bf9](c107bf9))
* harden personality profiles, department validation, and template
rendering ([#158](#158))
([10b2299](10b2299))
* pre-PR review improvements for ExecutionLoop + ReAct loop
([#124](#124))
([8dfb3c0](8dfb3c0))
* split events.py into per-domain event modules
([#136](#136))
([e9cba89](e9cba89))


### Documentation

* add ADR-001 memory layer evaluation and selection
([#178](#178))
([db3026f](db3026f)),
closes [#39](#39)
* add agent scaling research findings to DESIGN_SPEC
([#145](#145))
([57e487b](57e487b))
* add CLAUDE.md, contributing guide, and dev documentation
([#65](#65))
([55c1025](55c1025)),
closes [#54](#54)
* add crash recovery, sandboxing, analytics, and testing decisions
([#127](#127))
([5c11595](5c11595))
* address external review feedback with MVP scope and new protocols
([#128](#128))
([3b30b9a](3b30b9a))
* expand design spec with pluggable strategy protocols
([#121](#121))
([6832db6](6832db6))
* finalize 23 design decisions (ADR-002)
([#190](#190))
([8c39742](8c39742))
* update project docs for M2.5 conventions and add docs-consistency
review agent
([#114](#114))
([99766ee](99766ee))


### Tests

* add e2e single agent integration tests
([#24](#24))
([#156](#156))
([f566fb4](f566fb4))
* add provider adapter integration tests
([#90](#90))
([40a61f4](40a61f4))


### CI/CD

* add Release Please for automated versioning and GitHub Releases
([#278](#278))
([a488758](a488758))
* bump actions/checkout from 4 to 6
([#95](#95))
([1897247](1897247))
* bump actions/upload-artifact from 4 to 7
([#94](#94))
([27b1517](27b1517))
* harden CI/CD pipeline
([#92](#92))
([ce4693c](ce4693c))
* split vulnerability scans into critical-fail and high-warn tiers
([#277](#277))
([aba48af](aba48af))


### Maintenance

* add /worktree skill for parallel worktree management
([#171](#171))
([951e337](951e337))
* add design spec context loading to research-link skill
([8ef9685](8ef9685))
* add post-merge-cleanup skill
([#70](#70))
([f913705](f913705))
* add pre-pr-review skill and update CLAUDE.md
([#103](#103))
([92e9023](92e9023))
* add research-link skill and rename skill files to SKILL.md
([#101](#101))
([651c577](651c577))
* bump aiosqlite from 0.21.0 to 0.22.1
([#191](#191))
([3274a86](3274a86))
* bump pyyaml from 6.0.2 to 6.0.3 in the minor-and-patch group
([#96](#96))
([0338d0c](0338d0c))
* bump ruff from 0.15.4 to 0.15.5
([a49ee46](a49ee46))
* fix M0 audit items
([#66](#66))
([c7724b5](c7724b5))
* pin setup-uv action to full SHA
([#281](#281))
([4448002](4448002))
* post-audit cleanup — PEP 758, loggers, bug fixes, refactoring, tests,
hookify rules
([#148](#148))
([c57a6a9](c57a6a9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Aureliolo added a commit that referenced this pull request Mar 11, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.0](v0.0.0...v0.1.0)
(2026-03-11)


### Features

* add autonomy levels and approval timeout policies
([#42](#42),
[#126](#126))
([#197](#197))
([eecc25a](eecc25a))
* add CFO cost optimization service with anomaly detection, reports, and
approval decisions
([#186](#186))
([a7fa00b](a7fa00b))
* add code quality toolchain (ruff, mypy, pre-commit, dependabot)
([#63](#63))
([36681a8](36681a8))
* add configurable cost tiers and subscription/quota-aware tracking
([#67](#67))
([#185](#185))
([9baedfa](9baedfa))
* add container packaging, Docker Compose, and CI pipeline
([#269](#269))
([435bdfe](435bdfe)),
closes [#267](#267)
* add coordination error taxonomy classification pipeline
([#146](#146))
([#181](#181))
([70c7480](70c7480))
* add cost-optimized, hierarchical, and auction assignment strategies
([#175](#175))
([ce924fa](ce924fa)),
closes [#173](#173)
* add design specification, license, and project setup
([8669a09](8669a09))
* add env var substitution and config file auto-discovery
([#77](#77))
([7f53832](7f53832))
* add FastestStrategy routing + vendor-agnostic cleanup
([#140](#140))
([09619cb](09619cb)),
closes [#139](#139)
* add HR engine and performance tracking
([#45](#45),
[#47](#47))
([#193](#193))
([2d091ea](2d091ea))
* add issue auto-search and resolution verification to PR review skill
([#119](#119))
([deecc39](deecc39))
* add mandatory JWT + API key authentication
([#256](#256))
([c279cfe](c279cfe))
* add memory retrieval, ranking, and context injection pipeline
([#41](#41))
([873b0aa](873b0aa))
* add pluggable MemoryBackend protocol with models, config, and events
([#180](#180))
([46cfdd4](46cfdd4))
* add pluggable MemoryBackend protocol with models, config, and events
([#32](#32))
([46cfdd4](46cfdd4))
* add pluggable output scan response policies
([#263](#263))
([b9907e8](b9907e8))
* add pluggable PersistenceBackend protocol with SQLite implementation
([#36](#36))
([f753779](f753779))
* add progressive trust and promotion/demotion subsystems
([#43](#43),
[#49](#49))
([3a87c08](3a87c08))
* add retry handler, rate limiter, and provider resilience
([#100](#100))
([b890545](b890545))
* add SecOps security agent with rule engine, audit log, and ToolInvoker
integration ([#40](#40))
([83b7b6c](83b7b6c))
* add shared org memory and memory consolidation/archival
([#125](#125),
[#48](#48))
([4a0832b](4a0832b))
* design unified provider interface
([#86](#86))
([3e23d64](3e23d64))
* expand template presets, rosters, and add inheritance
([#80](#80),
[#81](#81),
[#84](#84))
([15a9134](15a9134))
* implement agent runtime state vs immutable config split
([#115](#115))
([4cb1ca5](4cb1ca5))
* implement AgentEngine core orchestrator
([#11](#11))
([#143](#143))
([f2eb73a](f2eb73a))
* implement AuditRepository for security audit log persistence
([#279](#279))
([94bc29f](94bc29f))
* implement basic tool system (registry, invocation, results)
([#15](#15))
([c51068b](c51068b))
* implement built-in file system tools
([#18](#18))
([325ef98](325ef98))
* implement communication foundation — message bus, dispatcher, and
messenger ([#157](#157))
([8e71bfd](8e71bfd))
* implement company template system with 7 built-in presets
([#85](#85))
([cbf1496](cbf1496))
* implement conflict resolution protocol
([#122](#122))
([#166](#166))
([e03f9f2](e03f9f2))
* implement core entity and role system models
([#69](#69))
([acf9801](acf9801))
* implement crash recovery with fail-and-reassign strategy
([#149](#149))
([e6e91ed](e6e91ed))
* implement engine extensions — Plan-and-Execute loop and call
categorization
([#134](#134),
[#135](#135))
([#159](#159))
([9b2699f](9b2699f))
* implement enterprise logging system with structlog
([#73](#73))
([2f787e5](2f787e5))
* implement graceful shutdown with cooperative timeout strategy
([#130](#130))
([6592515](6592515))
* implement hierarchical delegation and loop prevention
([#12](#12),
[#17](#17))
([6be60b6](6be60b6))
* implement LiteLLM driver and provider registry
([#88](#88))
([ae3f18b](ae3f18b)),
closes [#4](#4)
* implement LLM decomposition strategy and workspace isolation
([#174](#174))
([aa0eefe](aa0eefe))
* implement meeting protocol system
([#123](#123))
([ee7caca](ee7caca))
* implement message and communication domain models
([#74](#74))
([560a5d2](560a5d2))
* implement model routing engine
([#99](#99))
([d3c250b](d3c250b))
* implement parallel agent execution
([#22](#22))
([#161](#161))
([65940b3](65940b3))
* implement per-call cost tracking service
([#7](#7))
([#102](#102))
([c4f1f1c](c4f1f1c))
* implement personality injection and system prompt construction
([#105](#105))
([934dd85](934dd85))
* implement single-task execution lifecycle
([#21](#21))
([#144](#144))
([c7e64e4](c7e64e4))
* implement subprocess sandbox for tool execution isolation
([#131](#131))
([#153](#153))
([3c8394e](3c8394e))
* implement task assignment subsystem with pluggable strategies
([#172](#172))
([c7f1b26](c7f1b26)),
closes [#26](#26)
[#30](#30)
* implement task decomposition and routing engine
([#14](#14))
([9c7fb52](9c7fb52))
* implement Task, Project, Artifact, Budget, and Cost domain models
([#71](#71))
([81eabf1](81eabf1))
* implement tool permission checking
([#16](#16))
([833c190](833c190))
* implement YAML config loader with Pydantic validation
([#59](#59))
([ff3a2ba](ff3a2ba))
* implement YAML config loader with Pydantic validation
([#75](#75))
([ff3a2ba](ff3a2ba))
* initialize project with uv, hatchling, and src layout
([39005f9](39005f9))
* initialize project with uv, hatchling, and src layout
([#62](#62))
([39005f9](39005f9))
* Litestar REST API, WebSocket feed, and approval queue (M6)
([#189](#189))
([29fcd08](29fcd08))
* make TokenUsage.total_tokens a computed field
([#118](#118))
([c0bab18](c0bab18)),
closes [#109](#109)
* parallel tool execution in ToolInvoker.invoke_all
([#137](#137))
([58517ee](58517ee))
* testing framework, CI pipeline, and M0 gap fixes
([#64](#64))
([f581749](f581749))
* wire all modules into observability system
([#97](#97))
([f7a0617](f7a0617))


### Bug Fixes

* address Greptile post-merge review findings from PRs
[#170](https://github.com/Aureliolo/ai-company/issues/170)-[#175](https://github.com/Aureliolo/ai-company/issues/175)
([#176](#176))
([c5ca929](c5ca929))
* address post-merge review feedback from PRs
[#164](https://github.com/Aureliolo/ai-company/issues/164)-[#167](https://github.com/Aureliolo/ai-company/issues/167)
([#170](#170))
([3bf897a](3bf897a)),
closes [#169](#169)
* enforce strict mypy on test files
([#89](#89))
([aeeff8c](aeeff8c))
* harden Docker sandbox, MCP bridge, and code runner
([#50](#50),
[#53](#53))
([d5e1b6e](d5e1b6e))
* harden git tools security + code quality improvements
([#150](#150))
([000a325](000a325))
* harden subprocess cleanup, env filtering, and shutdown resilience
([#155](#155))
([d1fe1fb](d1fe1fb))
* incorporate post-merge feedback + pre-PR review fixes
([#164](#164))
([c02832a](c02832a))
* pre-PR review fixes for post-merge findings
([#183](#183))
([26b3108](26b3108))
* resolve circular imports, bump litellm, fix release tag format
([#286](#286))
([a6659b5](a6659b5))
* strengthen immutability for BaseTool schema and ToolInvoker boundaries
([#117](#117))
([7e5e861](7e5e861))


### Performance

* harden non-inferable principle implementation
([#195](#195))
([02b5f4e](02b5f4e)),
closes [#188](#188)


### Refactoring

* adopt NotBlankStr across all models
([#108](#108))
([#120](#120))
([ef89b90](ef89b90))
* extract _SpendingTotals base class from spending summary models
([#111](#111))
([2f39c1b](2f39c1b))
* harden BudgetEnforcer with error handling, validation extraction, and
review fixes
([#182](#182))
([c107bf9](c107bf9))
* harden personality profiles, department validation, and template
rendering ([#158](#158))
([10b2299](10b2299))
* pre-PR review improvements for ExecutionLoop + ReAct loop
([#124](#124))
([8dfb3c0](8dfb3c0))
* split events.py into per-domain event modules
([#136](#136))
([e9cba89](e9cba89))


### Documentation

* add ADR-001 memory layer evaluation and selection
([#178](#178))
([db3026f](db3026f)),
closes [#39](#39)
* add agent scaling research findings to DESIGN_SPEC
([#145](#145))
([57e487b](57e487b))
* add CLAUDE.md, contributing guide, and dev documentation
([#65](#65))
([55c1025](55c1025)),
closes [#54](#54)
* add crash recovery, sandboxing, analytics, and testing decisions
([#127](#127))
([5c11595](5c11595))
* address external review feedback with MVP scope and new protocols
([#128](#128))
([3b30b9a](3b30b9a))
* expand design spec with pluggable strategy protocols
([#121](#121))
([6832db6](6832db6))
* finalize 23 design decisions (ADR-002)
([#190](#190))
([8c39742](8c39742))
* update project docs for M2.5 conventions and add docs-consistency
review agent
([#114](#114))
([99766ee](99766ee))


### Tests

* add e2e single agent integration tests
([#24](#24))
([#156](#156))
([f566fb4](f566fb4))
* add provider adapter integration tests
([#90](#90))
([40a61f4](40a61f4))


### CI/CD

* add Release Please for automated versioning and GitHub Releases
([#278](#278))
([a488758](a488758))
* bump actions/checkout from 4 to 6
([#95](#95))
([1897247](1897247))
* bump actions/upload-artifact from 4 to 7
([#94](#94))
([27b1517](27b1517))
* bump anchore/scan-action from 6.5.1 to 7.3.2
([#271](#271))
([80a1c15](80a1c15))
* bump docker/build-push-action from 6.19.2 to 7.0.0
([#273](#273))
([dd0219e](dd0219e))
* bump docker/login-action from 3.7.0 to 4.0.0
([#272](#272))
([33d6238](33d6238))
* bump docker/metadata-action from 5.10.0 to 6.0.0
([#270](#270))
([baee04e](baee04e))
* bump docker/setup-buildx-action from 3.12.0 to 4.0.0
([#274](#274))
([5fc06f7](5fc06f7))
* bump sigstore/cosign-installer from 3.9.1 to 4.1.0
([#275](#275))
([29dd16c](29dd16c))
* harden CI/CD pipeline
([#92](#92))
([ce4693c](ce4693c))
* split vulnerability scans into critical-fail and high-warn tiers
([#277](#277))
([aba48af](aba48af))


### Maintenance

* add /worktree skill for parallel worktree management
([#171](#171))
([951e337](951e337))
* add design spec context loading to research-link skill
([8ef9685](8ef9685))
* add post-merge-cleanup skill
([#70](#70))
([f913705](f913705))
* add pre-pr-review skill and update CLAUDE.md
([#103](#103))
([92e9023](92e9023))
* add research-link skill and rename skill files to SKILL.md
([#101](#101))
([651c577](651c577))
* bump aiosqlite from 0.21.0 to 0.22.1
([#191](#191))
([3274a86](3274a86))
* bump pyyaml from 6.0.2 to 6.0.3 in the minor-and-patch group
([#96](#96))
([0338d0c](0338d0c))
* bump ruff from 0.15.4 to 0.15.5
([a49ee46](a49ee46))
* fix M0 audit items
([#66](#66))
([c7724b5](c7724b5))
* **main:** release ai-company 0.1.1
([#282](#282))
([2f4703d](2f4703d))
* pin setup-uv action to full SHA
([#281](#281))
([4448002](4448002))
* post-audit cleanup — PEP 758, loggers, bug fixes, refactoring, tests,
hookify rules
([#148](#148))
([c57a6a9](c57a6a9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: Aurelio <19254254+Aureliolo@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Define default configurations and company template schema

2 participants